mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
MAESTRO: Add iterate mode document merging to inline wizard
- Created wizard-inline-iterate-generation.md prompt for generating/updating documents in iterate mode with existing document context - Updated parseGeneratedDocuments() to detect UPDATE: true marker that signals when documents should update existing files vs create new ones - Modified generateDocumentPrompt() to use iterate-specific prompt when mode is 'iterate', including existing docs content and user's goal - Added isUpdate field to ParsedDocument interface for tracking update intent - Updated saveDocument() to log whether creating or updating files - Added 24 comprehensive tests for document parsing and iterate mode features The iterate mode now supports: - Creating new phase files (Phase-03-NewFeature.md) for new work - Updating existing phase files when marked with UPDATE: true - Mixed operations (some updates, some new) in a single generation
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
/**
|
||||
* Tests for inlineWizardDocumentGeneration.ts
|
||||
*
|
||||
* These tests verify the document parsing and iterate mode functionality.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
parseGeneratedDocuments,
|
||||
splitIntoPhases,
|
||||
sanitizeFilename,
|
||||
countTasks,
|
||||
} from '../../../renderer/services/inlineWizardDocumentGeneration';
|
||||
|
||||
describe('inlineWizardDocumentGeneration', () => {
|
||||
describe('parseGeneratedDocuments', () => {
|
||||
it('should parse documents with standard markers', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-Setup.md
|
||||
CONTENT:
|
||||
# Phase 01: Setup
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Install dependencies
|
||||
- [ ] Configure project
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].filename).toBe('Phase-01-Setup.md');
|
||||
expect(docs[0].phase).toBe(1);
|
||||
expect(docs[0].isUpdate).toBe(false);
|
||||
expect(docs[0].content).toContain('# Phase 01: Setup');
|
||||
expect(docs[0].content).toContain('- [ ] Install dependencies');
|
||||
});
|
||||
|
||||
it('should parse multiple documents', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-Setup.md
|
||||
CONTENT:
|
||||
# Phase 01: Setup
|
||||
|
||||
- [ ] Task 1
|
||||
---END DOCUMENT---
|
||||
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-02-Build.md
|
||||
CONTENT:
|
||||
# Phase 02: Build
|
||||
|
||||
- [ ] Task 2
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(2);
|
||||
expect(docs[0].filename).toBe('Phase-01-Setup.md');
|
||||
expect(docs[0].phase).toBe(1);
|
||||
expect(docs[1].filename).toBe('Phase-02-Build.md');
|
||||
expect(docs[1].phase).toBe(2);
|
||||
});
|
||||
|
||||
it('should detect UPDATE marker for iterate mode', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-Setup.md
|
||||
UPDATE: true
|
||||
CONTENT:
|
||||
# Phase 01: Setup (Updated)
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Updated task 1
|
||||
- [ ] New task added
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].filename).toBe('Phase-01-Setup.md');
|
||||
expect(docs[0].isUpdate).toBe(true);
|
||||
expect(docs[0].content).toContain('(Updated)');
|
||||
});
|
||||
|
||||
it('should handle UPDATE: false explicitly', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-03-NewFeature.md
|
||||
UPDATE: false
|
||||
CONTENT:
|
||||
# Phase 03: New Feature
|
||||
|
||||
- [ ] New task
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].isUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle mixed update and new documents', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-Setup.md
|
||||
UPDATE: true
|
||||
CONTENT:
|
||||
# Phase 01: Setup (Updated)
|
||||
|
||||
- [ ] Updated task
|
||||
---END DOCUMENT---
|
||||
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-03-NewFeature.md
|
||||
CONTENT:
|
||||
# Phase 03: New Feature
|
||||
|
||||
- [ ] New feature task
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(2);
|
||||
expect(docs[0].filename).toBe('Phase-01-Setup.md');
|
||||
expect(docs[0].isUpdate).toBe(true);
|
||||
expect(docs[0].phase).toBe(1);
|
||||
expect(docs[1].filename).toBe('Phase-03-NewFeature.md');
|
||||
expect(docs[1].isUpdate).toBe(false);
|
||||
expect(docs[1].phase).toBe(3);
|
||||
});
|
||||
|
||||
it('should sort documents by phase number', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-03-Deploy.md
|
||||
CONTENT:
|
||||
# Phase 03
|
||||
|
||||
- [ ] Task
|
||||
---END DOCUMENT---
|
||||
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-Setup.md
|
||||
CONTENT:
|
||||
# Phase 01
|
||||
|
||||
- [ ] Task
|
||||
---END DOCUMENT---
|
||||
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-02-Build.md
|
||||
CONTENT:
|
||||
# Phase 02
|
||||
|
||||
- [ ] Task
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(3);
|
||||
expect(docs[0].phase).toBe(1);
|
||||
expect(docs[1].phase).toBe(2);
|
||||
expect(docs[2].phase).toBe(3);
|
||||
});
|
||||
|
||||
it('should handle documents without phase numbers in filename', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: README.md
|
||||
CONTENT:
|
||||
# Project README
|
||||
|
||||
Some content here.
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].filename).toBe('README.md');
|
||||
expect(docs[0].phase).toBe(0);
|
||||
expect(docs[0].isUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle empty output', () => {
|
||||
const docs = parseGeneratedDocuments('');
|
||||
expect(docs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle output without document markers', () => {
|
||||
const output = 'Just some random text without markers';
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
expect(docs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle UPDATE marker case-insensitively', () => {
|
||||
const output = `
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-Setup.md
|
||||
UPDATE: TRUE
|
||||
CONTENT:
|
||||
# Phase 01
|
||||
|
||||
- [ ] Task
|
||||
---END DOCUMENT---
|
||||
`;
|
||||
|
||||
const docs = parseGeneratedDocuments(output);
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].isUpdate).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('splitIntoPhases', () => {
|
||||
it('should split content with phase headers', () => {
|
||||
const content = `
|
||||
# Phase 1: Setup
|
||||
|
||||
- [ ] Task 1
|
||||
|
||||
# Phase 2: Build
|
||||
|
||||
- [ ] Task 2
|
||||
`;
|
||||
|
||||
const docs = splitIntoPhases(content);
|
||||
|
||||
expect(docs).toHaveLength(2);
|
||||
expect(docs[0].phase).toBe(1);
|
||||
expect(docs[1].phase).toBe(2);
|
||||
expect(docs[0].isUpdate).toBe(false);
|
||||
expect(docs[1].isUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('should treat content without phases as Phase 1', () => {
|
||||
const content = `
|
||||
# Some Document
|
||||
|
||||
- [ ] Task 1
|
||||
- [ ] Task 2
|
||||
`;
|
||||
|
||||
const docs = splitIntoPhases(content);
|
||||
|
||||
expect(docs).toHaveLength(1);
|
||||
expect(docs[0].filename).toBe('Phase-01-Initial-Setup.md');
|
||||
expect(docs[0].phase).toBe(1);
|
||||
expect(docs[0].isUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle empty content', () => {
|
||||
const docs = splitIntoPhases('');
|
||||
expect(docs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should extract description from phase header', () => {
|
||||
const content = `
|
||||
# Phase 1: Project Configuration
|
||||
|
||||
- [ ] Configure project
|
||||
|
||||
# Phase 2: Core Implementation
|
||||
|
||||
- [ ] Implement core
|
||||
`;
|
||||
|
||||
const docs = splitIntoPhases(content);
|
||||
|
||||
expect(docs).toHaveLength(2);
|
||||
expect(docs[0].filename).toContain('Phase-01');
|
||||
expect(docs[0].filename).toContain('Project-Configuration');
|
||||
expect(docs[1].filename).toContain('Phase-02');
|
||||
expect(docs[1].filename).toContain('Core-Implementation');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizeFilename', () => {
|
||||
it('should remove path separators', () => {
|
||||
expect(sanitizeFilename('path/to/file.md')).toBe('path-to-file.md');
|
||||
expect(sanitizeFilename('path\\to\\file.md')).toBe('path-to-file.md');
|
||||
});
|
||||
|
||||
it('should remove directory traversal sequences', () => {
|
||||
// Path separators become dashes, .. is removed, leading dots are stripped
|
||||
expect(sanitizeFilename('../../../etc/passwd')).toBe('---etc-passwd');
|
||||
expect(sanitizeFilename('..file.md')).toBe('file.md');
|
||||
});
|
||||
|
||||
it('should remove leading dots', () => {
|
||||
expect(sanitizeFilename('.hidden')).toBe('hidden');
|
||||
expect(sanitizeFilename('...file')).toBe('file');
|
||||
});
|
||||
|
||||
it('should return "document" for empty result', () => {
|
||||
expect(sanitizeFilename('')).toBe('document');
|
||||
expect(sanitizeFilename('...')).toBe('document');
|
||||
// Forward slash becomes dash
|
||||
expect(sanitizeFilename('/')).toBe('-');
|
||||
});
|
||||
|
||||
it('should trim whitespace', () => {
|
||||
expect(sanitizeFilename(' file.md ')).toBe('file.md');
|
||||
});
|
||||
});
|
||||
|
||||
describe('countTasks', () => {
|
||||
it('should count unchecked tasks', () => {
|
||||
const content = `
|
||||
# Tasks
|
||||
|
||||
- [ ] Task 1
|
||||
- [ ] Task 2
|
||||
- [ ] Task 3
|
||||
`;
|
||||
expect(countTasks(content)).toBe(3);
|
||||
});
|
||||
|
||||
it('should count checked tasks', () => {
|
||||
const content = `
|
||||
# Tasks
|
||||
|
||||
- [x] Done task 1
|
||||
- [X] Done task 2
|
||||
`;
|
||||
expect(countTasks(content)).toBe(2);
|
||||
});
|
||||
|
||||
it('should count mixed tasks', () => {
|
||||
const content = `
|
||||
# Tasks
|
||||
|
||||
- [ ] Todo 1
|
||||
- [x] Done 1
|
||||
- [ ] Todo 2
|
||||
- [X] Done 2
|
||||
`;
|
||||
expect(countTasks(content)).toBe(4);
|
||||
});
|
||||
|
||||
it('should return 0 for content without tasks', () => {
|
||||
const content = '# Just a heading\n\nSome text.';
|
||||
expect(countTasks(content)).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle empty content', () => {
|
||||
expect(countTasks('')).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,7 @@ export {
|
||||
wizardInlineSystemPrompt,
|
||||
wizardInlineIteratePrompt,
|
||||
wizardInlineNewPrompt,
|
||||
wizardInlineIterateGenerationPrompt,
|
||||
|
||||
// AutoRun
|
||||
autorunDefaultPrompt,
|
||||
|
||||
88
src/prompts/wizard-inline-iterate-generation.md
Normal file
88
src/prompts/wizard-inline-iterate-generation.md
Normal file
@@ -0,0 +1,88 @@
|
||||
You are an expert project planner creating actionable task documents for "{{PROJECT_NAME}}".
|
||||
|
||||
## Your Task
|
||||
|
||||
Based on the project discovery conversation below, create or update Auto Run documents. The user has existing documents and wants to extend or modify their plans.
|
||||
|
||||
## Working Directory
|
||||
|
||||
All files will be created or updated in: {{DIRECTORY_PATH}}
|
||||
The documents folder: {{DIRECTORY_PATH}}/{{AUTO_RUN_FOLDER_NAME}}/
|
||||
|
||||
## Existing Documents
|
||||
|
||||
The following Auto Run documents already exist:
|
||||
|
||||
{{EXISTING_DOCS}}
|
||||
|
||||
## User's Goal
|
||||
|
||||
{{ITERATE_GOAL}}
|
||||
|
||||
## Iterate Mode Guidelines
|
||||
|
||||
You can either:
|
||||
1. **Create new phase files** (e.g., Phase-03-NewFeature.md) when adding entirely new work
|
||||
2. **Update existing files** when modifying or extending current phases
|
||||
|
||||
When deciding:
|
||||
- Add a NEW phase if the work is independent and follows existing phases
|
||||
- UPDATE an existing phase if the work extends or modifies that phase's scope
|
||||
- You can do BOTH: update an existing phase AND create new phases
|
||||
|
||||
## Document Format
|
||||
|
||||
Each Auto Run document MUST follow this exact format:
|
||||
|
||||
```markdown
|
||||
# Phase XX: [Brief Title]
|
||||
|
||||
[One paragraph describing what this phase accomplishes and why it matters]
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] First specific task to complete
|
||||
- [ ] Second specific task to complete
|
||||
- [ ] Continue with more tasks...
|
||||
```
|
||||
|
||||
## Task Writing Guidelines
|
||||
|
||||
Each task should be:
|
||||
- **Specific**: Not "set up the project" but "Create package.json with required dependencies"
|
||||
- **Actionable**: Clear what needs to be done
|
||||
- **Verifiable**: You can tell when it's complete
|
||||
- **Autonomous**: Can be done without asking the user questions
|
||||
|
||||
## Output Format
|
||||
|
||||
For NEW documents, use this format:
|
||||
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-03-[Description].md
|
||||
CONTENT:
|
||||
[Full markdown content here]
|
||||
---END DOCUMENT---
|
||||
|
||||
For UPDATED documents, use this format with the exact existing filename:
|
||||
|
||||
---BEGIN DOCUMENT---
|
||||
FILENAME: Phase-01-[ExactExistingName].md
|
||||
UPDATE: true
|
||||
CONTENT:
|
||||
[Complete updated markdown content - include the full document, not just changes]
|
||||
---END DOCUMENT---
|
||||
|
||||
**IMPORTANT**:
|
||||
- When updating, provide the COMPLETE updated document content, not just the additions
|
||||
- Use the exact filename of the existing document you're updating
|
||||
- Write markdown content directly - do NOT wrap it in code fences
|
||||
- New phases should use the next available phase number
|
||||
|
||||
## Project Discovery Conversation
|
||||
|
||||
{{CONVERSATION_SUMMARY}}
|
||||
|
||||
## Now Generate the Documents
|
||||
|
||||
Based on the conversation above and the existing documents, create new phases and/or update existing phases as appropriate for the user's goal.
|
||||
@@ -12,7 +12,10 @@
|
||||
import type { ToolType } from '../types';
|
||||
import type { InlineWizardMessage, InlineGeneratedDocument } from '../hooks/useInlineWizard';
|
||||
import type { ExistingDocument } from '../utils/existingDocsDetector';
|
||||
import { wizardDocumentGenerationPrompt } from '../../prompts';
|
||||
import {
|
||||
wizardDocumentGenerationPrompt,
|
||||
wizardInlineIterateGenerationPrompt,
|
||||
} from '../../prompts';
|
||||
import { substituteTemplateVariables, type TemplateContext } from '../utils/templateVariables';
|
||||
|
||||
/**
|
||||
@@ -88,6 +91,8 @@ interface ParsedDocument {
|
||||
filename: string;
|
||||
content: string;
|
||||
phase: number;
|
||||
/** Whether this document updates an existing file (vs creating new) */
|
||||
isUpdate: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,14 +126,36 @@ export function countTasks(content: string): number {
|
||||
return matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format existing documents for inclusion in the iterate prompt.
|
||||
*
|
||||
* @param docs - Array of existing documents with content
|
||||
* @returns Formatted string for the prompt
|
||||
*/
|
||||
function formatExistingDocsForPrompt(docs: ExistingDocument[]): string {
|
||||
if (!docs || docs.length === 0) {
|
||||
return '(No existing documents found)';
|
||||
}
|
||||
|
||||
return docs
|
||||
.map((doc, index) => {
|
||||
const content = (doc as ExistingDocument & { content?: string }).content || '(Content not loaded)';
|
||||
return `### ${index + 1}. ${doc.filename}\n\n${content}`;
|
||||
})
|
||||
.join('\n\n---\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the document generation prompt.
|
||||
*
|
||||
* Uses the iterate-specific prompt when in iterate mode, which includes
|
||||
* existing documents and the user's goal for extending/modifying plans.
|
||||
*
|
||||
* @param config Configuration for generation
|
||||
* @returns The complete prompt for the agent
|
||||
*/
|
||||
function generateDocumentPrompt(config: DocumentGenerationConfig): string {
|
||||
const { projectName, directoryPath, conversationHistory } = config;
|
||||
const { projectName, directoryPath, conversationHistory, mode, goal, existingDocuments } = config;
|
||||
const projectDisplay = projectName || 'this project';
|
||||
|
||||
// Build conversation summary from the wizard conversation
|
||||
@@ -140,13 +167,28 @@ function generateDocumentPrompt(config: DocumentGenerationConfig): string {
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
// Choose the appropriate prompt template based on mode
|
||||
const basePrompt = mode === 'iterate'
|
||||
? wizardInlineIterateGenerationPrompt
|
||||
: wizardDocumentGenerationPrompt;
|
||||
|
||||
// Handle wizard-specific template variables
|
||||
let prompt = wizardDocumentGenerationPrompt
|
||||
let prompt = basePrompt
|
||||
.replace(/\{\{PROJECT_NAME\}\}/gi, projectDisplay)
|
||||
.replace(/\{\{DIRECTORY_PATH\}\}/gi, directoryPath)
|
||||
.replace(/\{\{AUTO_RUN_FOLDER_NAME\}\}/gi, AUTO_RUN_FOLDER_NAME)
|
||||
.replace(/\{\{CONVERSATION_SUMMARY\}\}/gi, conversationSummary);
|
||||
|
||||
// Handle iterate-mode specific placeholders
|
||||
if (mode === 'iterate') {
|
||||
const existingDocsText = formatExistingDocsForPrompt(existingDocuments || []);
|
||||
const iterateGoal = goal || '(No specific goal provided)';
|
||||
|
||||
prompt = prompt
|
||||
.replace(/\{\{EXISTING_DOCS\}\}/gi, existingDocsText)
|
||||
.replace(/\{\{ITERATE_GOAL\}\}/gi, iterateGoal);
|
||||
}
|
||||
|
||||
// Build template context for remaining variables
|
||||
const templateContext: TemplateContext = {
|
||||
session: {
|
||||
@@ -170,20 +212,38 @@ function generateDocumentPrompt(config: DocumentGenerationConfig): string {
|
||||
* Looks for document blocks with markers:
|
||||
* ---BEGIN DOCUMENT---
|
||||
* FILENAME: Phase-01-Setup.md
|
||||
* UPDATE: true (optional - indicates this updates an existing file)
|
||||
* CONTENT:
|
||||
* [markdown content]
|
||||
* ---END DOCUMENT---
|
||||
*
|
||||
* When UPDATE: true is present, the document will overwrite an existing file.
|
||||
* Otherwise, it creates a new file.
|
||||
*/
|
||||
export function parseGeneratedDocuments(output: string): ParsedDocument[] {
|
||||
const documents: ParsedDocument[] = [];
|
||||
|
||||
// Pattern to match document blocks
|
||||
const docPattern = /---BEGIN DOCUMENT---\s*\nFILENAME:\s*(.+?)\s*\nCONTENT:\s*\n([\s\S]*?)(?=---END DOCUMENT---|$)/g;
|
||||
// Split by document markers and process each block
|
||||
const blocks = output.split(/---BEGIN DOCUMENT---/);
|
||||
|
||||
let match;
|
||||
while ((match = docPattern.exec(output)) !== null) {
|
||||
const filename = match[1].trim();
|
||||
let content = match[2].trim();
|
||||
for (const block of blocks) {
|
||||
if (!block.trim()) continue;
|
||||
|
||||
// Extract filename
|
||||
const filenameMatch = block.match(/FILENAME:\s*(.+?)(?:\n|$)/);
|
||||
if (!filenameMatch) continue;
|
||||
|
||||
const filename = filenameMatch[1].trim();
|
||||
|
||||
// Check for UPDATE marker (optional)
|
||||
const updateMatch = block.match(/UPDATE:\s*(true|false)/i);
|
||||
const isUpdate = updateMatch ? updateMatch[1].toLowerCase() === 'true' : false;
|
||||
|
||||
// Extract content - everything after "CONTENT:" line
|
||||
const contentMatch = block.match(/CONTENT:\s*\n([\s\S]*?)(?=---END DOCUMENT---|$)/);
|
||||
if (!contentMatch) continue;
|
||||
|
||||
let content = contentMatch[1].trim();
|
||||
|
||||
// Remove any trailing ---END DOCUMENT--- marker from content
|
||||
content = content.replace(/---END DOCUMENT---\s*$/, '').trim();
|
||||
@@ -197,6 +257,7 @@ export function parseGeneratedDocuments(output: string): ParsedDocument[] {
|
||||
filename,
|
||||
content,
|
||||
phase,
|
||||
isUpdate,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -212,6 +273,9 @@ export function parseGeneratedDocuments(output: string): ParsedDocument[] {
|
||||
*
|
||||
* If the agent generates one large document instead of multiple phases,
|
||||
* this function attempts to split it intelligently.
|
||||
*
|
||||
* Note: Documents created by splitting are always treated as new (isUpdate: false)
|
||||
* since we can't determine intent from raw content.
|
||||
*/
|
||||
export function splitIntoPhases(content: string): ParsedDocument[] {
|
||||
const documents: ParsedDocument[] = [];
|
||||
@@ -239,6 +303,7 @@ export function splitIntoPhases(content: string): ParsedDocument[] {
|
||||
filename: `Phase-${String(phaseNumber).padStart(2, '0')}-${description}.md`,
|
||||
content: fullContent,
|
||||
phase: phaseNumber,
|
||||
isUpdate: false,
|
||||
});
|
||||
|
||||
phaseNumber++;
|
||||
@@ -250,6 +315,7 @@ export function splitIntoPhases(content: string): ParsedDocument[] {
|
||||
filename: 'Phase-01-Initial-Setup.md',
|
||||
content: content.trim(),
|
||||
phase: 1,
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -355,6 +421,10 @@ function buildArgsForAgent(agent: { id: string; args?: string[] }): string[] {
|
||||
/**
|
||||
* Save a single document to the Auto Run folder.
|
||||
*
|
||||
* Handles both creating new files and updating existing ones.
|
||||
* The isUpdate flag is used for logging purposes - both operations
|
||||
* use writeDoc which will create or overwrite as needed.
|
||||
*
|
||||
* @param autoRunFolderPath - The Auto Run folder path
|
||||
* @param doc - The parsed document to save
|
||||
* @returns The saved document with path information
|
||||
@@ -368,9 +438,10 @@ async function saveDocument(
|
||||
// Ensure filename has .md extension
|
||||
const filename = sanitized.endsWith('.md') ? sanitized : `${sanitized}.md`;
|
||||
|
||||
console.log('[InlineWizardDocGen] Saving document:', filename);
|
||||
const action = doc.isUpdate ? 'Updating' : 'Creating';
|
||||
console.log(`[InlineWizardDocGen] ${action} document:`, filename);
|
||||
|
||||
// Write the document
|
||||
// Write the document (creates or overwrites as needed)
|
||||
const result = await window.maestro.autorun.writeDoc(
|
||||
autoRunFolderPath,
|
||||
filename,
|
||||
@@ -378,7 +449,7 @@ async function saveDocument(
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || `Failed to save ${filename}`);
|
||||
throw new Error(result.error || `Failed to ${action.toLowerCase()} ${filename}`);
|
||||
}
|
||||
|
||||
const fullPath = `${autoRunFolderPath}/${filename}`;
|
||||
@@ -600,6 +671,9 @@ export async function generateInlineDocuments(
|
||||
*
|
||||
* This is a fallback for when the agent writes files directly
|
||||
* instead of outputting them with markers.
|
||||
*
|
||||
* Note: Documents read from disk are treated as new (isUpdate: false)
|
||||
* since they were written directly by the agent.
|
||||
*/
|
||||
async function readDocumentsFromDisk(autoRunFolderPath: string): Promise<ParsedDocument[]> {
|
||||
const documents: ParsedDocument[] = [];
|
||||
@@ -626,6 +700,7 @@ async function readDocumentsFromDisk(autoRunFolderPath: string): Promise<ParsedD
|
||||
filename,
|
||||
content: readResult.content,
|
||||
phase,
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user