Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/prompts/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
export const UPDATE_SCAFFOLD_PROMPT_FALLBACK = `# Prompt: Update Repository Documentation and Agent Playbooks
export const UPDATE_SCAFFOLD_PROMPT_FALLBACK = `# Prompt: Update Repository Docs, Skills, and Agent Playbooks

## Purpose
You are an AI assistant responsible for refreshing the documentation (\`docs/\`) and agent playbooks (\`agents/\`). Your goal is to bring every guide up to date with the latest repository state and maintain cross-references between docs and agent instructions.
You are an AI assistant responsible for refreshing documentation (\`docs/\`), skills (\`skills/\`), and agent playbooks (\`agents/\`). Your goal is to bring every guide up to date with the latest repository state and maintain cross-references.

## Context Gathering
1. Review the repository structure and recent changes.
2. Inspect \`package.json\`, CI configuration, and any release or roadmap notes.
3. Check \`docs/README.md\` for the current document map.

## Update Procedure
1. **Update Documentation**
1. **Update Documentation (docs/)**
- Replace TODO placeholders with accurate, current information.
- Verify that links between docs remain valid.
- If you add new guides or sections, update \`docs/README.md\`.

2. **Agent Playbook Alignment**
2. **Update Skills (skills/)**
- Ensure each skill has codebase-specific activation rules and steps.
- Add concrete command/examples using repository conventions.
- Keep scope narrow and task-driven per skill.

3. **Agent Playbook Alignment (agents/)**
- For each change in \`docs/\`, adjust the related \`agents/*.md\` playbooks.
- Update responsibilities, best practices, and documentation touchpoints.

4. **Required Fill Order**
- Fill files in this exact order: \`docs -> skills -> agents\`.

## Acceptance Criteria
- No unresolved TODO placeholders remain unless they require explicit human input.
- Skills contain concrete, codebase-specific instructions.
- Agent playbooks list accurate responsibilities and best practices.
- Changes are self-contained, well-formatted Markdown.

Expand Down
6 changes: 3 additions & 3 deletions src/services/ai/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ export const RequiredActionSchema = z.object({
order: z.number().describe('Sequence order for this action'),
actionType: z.enum(['WRITE_FILE', 'CALL_TOOL', 'VERIFY']).describe('Type of action to perform'),
filePath: z.string().describe('Absolute path to the file'),
fileType: z.enum(['doc', 'agent']).describe('Type of scaffold file'),
fileType: z.enum(['doc', 'skill', 'agent']).describe('Type of scaffold file'),
instructions: z.string().describe('Instructions for filling this file'),
suggestedContent: z.string().optional().describe('Pre-generated content to write to the file'),
status: ActionStatusEnum.describe('Current status of this action'),
Expand Down Expand Up @@ -357,11 +357,11 @@ export const InitializeContextOutputSchema = z.object({

// Fill instructions (the UPDATE_SCAFFOLD_PROMPT)
fillInstructions: z.string().optional()
.describe('Standard prompt with guidelines for HOW to fill the scaffolded files'),
.describe('Standard prompt with guidelines for HOW to fill the scaffolded files (order: docs -> skills -> agents)'),

// Pending writes (renamed from requiredActions for clarity)
pendingWrites: z.array(RequiredActionSchema).optional()
.describe('Files that MUST be written. Each has content ready to write.'),
.describe('Files that MUST be written in order. Each has content ready to write.'),

// Legacy: requiredActions (kept for backwards compatibility)
requiredActions: z.array(RequiredActionSchema).optional()
Expand Down
7 changes: 5 additions & 2 deletions src/services/ai/toolRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ WORKFLOW:
1. Call initializeContext - scaffolding is created
2. Response has status: "incomplete" with pendingWrites[] array
3. Read the fillInstructions field - it contains guidelines for HOW to fill
4. For EACH file in pendingWrites:
4. Fill in REQUIRED ORDER: docs -> skills -> agents
5. For EACH file in pendingWrites:
- Call fillSingleFile({ repoPath, filePath }) to get AI-generated content
- Call Write({ file_path, content: suggestedContent }) to save
5. ONLY after ALL writes succeed → report "initialization complete"
6. ONLY after ALL writes succeed → report "initialization complete"

IMPORTANT:
- status: "incomplete" means you MUST complete the pendingWrites
Expand Down Expand Up @@ -121,6 +122,7 @@ IMPORTANT: After calling this, write each suggestedContent to its corresponding
description: 'List scaffold files that need to be filled. Returns only file paths (no content).',
extendedDescription: `List scaffold files that need to be filled. Returns only file paths (no content).
Use this first to get the list, then call fillSingleFile for each file.
Required fill order is docs -> skills -> agents.
This is more efficient than fillScaffolding for large projects.`
},

Expand All @@ -129,6 +131,7 @@ This is more efficient than fillScaffolding for large projects.`
description: 'Generate suggested content for a single scaffold file.',
extendedDescription: `Generate suggested content for a single scaffold file.
Call listFilesToFill first to get file paths, then call this for each file.
If you choose files out of order, the tool returns a warning with the next required phase.
This avoids output size limits by processing one file at a time.`
}
};
Expand Down
127 changes: 127 additions & 0 deletions src/services/ai/tools/fillScaffoldingTool.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import {
cleanupSharedContext,
fillSingleFileTool,
listFilesToFillTool,
} from './fillScaffoldingTool';

const toolOptions = { toolCallId: 'test-call', messages: [] } as any;

async function writeUnfilled(filePath: string): Promise<void> {
const content = `---
status: unfilled
---

placeholder
`;
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, content, 'utf8');
}

async function writeFilled(filePath: string): Promise<void> {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, '# Filled\n', 'utf8');
}

describe('fillScaffoldingTool ordering', () => {
let tempDir: string;
let contextDir: string;
let agentFilePath: string;
let skillFilePath: string;

beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fill-scaffolding-tool-'));
contextDir = path.join(tempDir, '.context');
agentFilePath = path.join(contextDir, 'agents', 'code-reviewer.md');
skillFilePath = path.join(contextDir, 'skills', 'commit-message', 'SKILL.md');

await fs.mkdir(path.join(tempDir, 'src'), { recursive: true });
await fs.writeFile(path.join(tempDir, 'src', 'index.ts'), 'export const value = 42;\n');

await writeUnfilled(path.join(contextDir, 'docs', 'project-overview.md'));
await writeFilled(path.join(contextDir, 'docs', 'README.md'));
await writeUnfilled(skillFilePath);
await writeUnfilled(agentFilePath);
});

afterEach(async () => {
await cleanupSharedContext();
await fs.rm(tempDir, { recursive: true, force: true });
});

it('lists only unfilled files and keeps docs -> skills -> agents order', async () => {
const result = await listFilesToFillTool.execute!({
repoPath: tempDir,
target: 'all',
}, toolOptions);

const typedResult = result as {
success: boolean;
totalCount: number;
files: Array<{ type: 'doc' | 'skill' | 'agent' | 'plan'; relativePath: string }>;
};

expect(typedResult.success).toBe(true);
expect(typedResult.totalCount).toBe(3);
expect(typedResult.files.map((f) => f.type)).toEqual(['doc', 'skill', 'agent']);
expect(typedResult.files.some((f) => f.relativePath.toLowerCase() === 'docs/readme.md')).toBe(false);
});

it('warns when requesting agents while docs are still pending', async () => {
const result = await listFilesToFillTool.execute!({
repoPath: tempDir,
target: 'agents',
}, toolOptions);

const typedResult = result as {
warning?: string;
nextRequiredPhase?: string;
files: Array<{ type: string }>;
};

expect(typedResult.files).toHaveLength(1);
expect(typedResult.files[0]?.type).toBe('agent');
expect(typedResult.warning).toContain('Fill order warning');
expect(typedResult.nextRequiredPhase).toBe('docs');
});

it('detects skill files and includes warning when filling out of order', async () => {
const skillResult = await fillSingleFileTool.execute!({
repoPath: tempDir,
filePath: skillFilePath,
}, toolOptions);

const typedSkillResult = skillResult as {
success: boolean;
fileType: string;
documentName: string;
warning?: string;
nextRequiredPhase?: string;
};

expect(typedSkillResult.success).toBe(true);
expect(typedSkillResult.fileType).toBe('skill');
expect(typedSkillResult.documentName).toBe('commit-message');
expect(typedSkillResult.warning).toContain('Fill order warning');
expect(typedSkillResult.nextRequiredPhase).toBe('docs');

const agentResult = await fillSingleFileTool.execute!({
repoPath: tempDir,
filePath: agentFilePath,
}, toolOptions);

const typedAgentResult = agentResult as {
success: boolean;
fileType: string;
warning?: string;
nextRequiredPhase?: string;
};

expect(typedAgentResult.success).toBe(true);
expect(typedAgentResult.fileType).toBe('agent');
expect(typedAgentResult.warning).toContain('Fill order warning');
expect(typedAgentResult.nextRequiredPhase).toBe('docs');
});
});
Loading