From afc0705b11118ae2772ec4dc807b5dcf4cf1dc3f Mon Sep 17 00:00:00 2001 From: Jeremiah Snee <113928685+jeremiah-snee-openx@users.noreply.github.com> Date: Tue, 7 Oct 2025 19:36:06 -0500 Subject: [PATCH] Collections Improvement with Tasks Collection POC (#270) * add yaml block support * Add usage Section to Collections * new edge ai tasks collection with usage example from PR https://github.com/github/awesome-copilot/pull/159 * Update collections/edge-ai-tasks.collection.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update collections/edge-ai-tasks.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update collections/edge-ai-tasks.collection.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update collections/edge-ai-tasks.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixing some bugs in the update script --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Aaron Powell --- .schemas/collection.schema.json | 6 +- README.collections.md | 1 + collections/edge-ai-tasks.collection.yml | 90 ++++++++++++++ collections/edge-ai-tasks.md | 100 ++++++++++++++++ update-readme.js | 142 +++++++++++++++++------ yaml-parser.js | 75 ++++++++++-- 6 files changed, 368 insertions(+), 46 deletions(-) create mode 100644 collections/edge-ai-tasks.collection.yml create mode 100644 collections/edge-ai-tasks.md diff --git a/.schemas/collection.schema.json b/.schemas/collection.schema.json index e4efe3a..0119882 100644 --- a/.schemas/collection.schema.json +++ b/.schemas/collection.schema.json @@ -57,6 +57,10 @@ "type": "string", "description": "Type of the item", "enum": ["prompt", "instruction", "chat-mode"] + }, + "usage": { + "type": "string", + "description": "Optional usage context for the item" } } }, @@ -81,4 +85,4 @@ } } } -} \ No newline at end of file +} diff --git a/README.collections.md b/README.collections.md index b5db549..4b2df4e 100644 --- a/README.collections.md +++ b/README.collections.md @@ -25,5 +25,6 @@ Curated collections of related prompts, instructions, and chat modes organized a | [Power Apps Code Apps Development](collections/power-apps-code-apps.md) | Complete toolkit for Power Apps Code Apps development including project scaffolding, development standards, and expert guidance for building code-first applications with Power Platform integration. | 3 items | power-apps, power-platform, typescript, react, code-apps, dataverse, connectors | | [Project Planning & Management](collections/project-planning.md) | Tools and guidance for software project planning, feature breakdown, epic management, implementation planning, and task organization for development teams. | 17 items | planning, project-management, epic, feature, implementation, task, architecture, technical-spike | | [Security & Code Quality](collections/security-best-practices.md) | Security frameworks, accessibility guidelines, performance optimization, and code quality best practices for building secure, maintainable, and high-performance applications. | 6 items | security, accessibility, performance, code-quality, owasp, a11y, optimization, best-practices | +| [Tasks by microsoft/edge-ai](collections/edge-ai-tasks.md) | Task Researcher and Task Planner for intermediate to expert users and large codebases - Brought to you by microsoft/edge-ai | 3 items | architecture, planning, research, tasks, implementation | | [Technical Spike](collections/technical-spike.md) | Tools for creation, management and research of technical spikes to reduce unknowns and assumptions before proceeding to specification and implementation of solutions. | 2 items | technical-spike, assumption-testing, validation, research | | [Testing & Test Automation](collections/testing-automation.md) | Comprehensive collection for writing tests, test automation, and test-driven development including unit tests, integration tests, and end-to-end testing strategies. | 11 items | testing, tdd, automation, unit-tests, integration, playwright, jest, nunit | diff --git a/collections/edge-ai-tasks.collection.yml b/collections/edge-ai-tasks.collection.yml new file mode 100644 index 0000000..316e2de --- /dev/null +++ b/collections/edge-ai-tasks.collection.yml @@ -0,0 +1,90 @@ +id: edge-ai-tasks +name: Tasks by microsoft/edge-ai +description: Task Researcher and Task Planner for intermediate to expert users and large codebases - Brought to you by microsoft/edge-ai +tags: [architecture, planning, research, tasks, implementation] +items: + # Planning Chat Modes + - path: chatmodes/task-researcher.chatmode.md + kind: chat-mode + usage: | + Now you can iterate on research for your tasks! + + ```markdown, research.prompt.md + --- + mode: task-researcher + title: Research microsoft fabric realtime intelligence terraform support + --- + Review the microsoft documentation for fabric realtime intelligence + and come up with ideas on how to implement this support into our terraform components. + ``` + + Research is dumped out into a .copilot-tracking/research/*-research.md file and will include discoveries for GHCP along with examples and schema that will be useful during implementation. + + Also, task-researcher will provide additional ideas for implementation which you can work with GitHub Copilot on selecting the right one to focus on. + + - path: chatmodes/task-planner.chatmode.md + kind: chat-mode + usage: | + Also, task-researcher will provide additional ideas for implementation which you can work with GitHub Copilot on selecting the right one to focus on. + + ```markdown, task-plan.prompt.md + --- + mode: task-planner + title: Plan microsoft fabric realtime intelligence terraform support + --- + #file: .copilot-tracking/research/*-fabric-rti-blueprint-modification-research.md + Build a plan to support adding fabric rti to this project + ``` + + `task-planner` will help you create a plan for implementing your task(s). It will use your fully researched ideas or build new research if not already provided. + + `task-planner` will produce three (3) files that will be used by `task-implementation.instructions.md`. + + * `.copilot-tracking/plan/*-plan.instructions.md` + + * A newly generated instructions file that has the plan as a checklist of Phases and Tasks. + * `.copilot-tracking/details/*-details.md` + + * The details for the implementation, the plan file refers to this file for specific details (important if you have a big plan). + * `.copilot-tracking/prompts/implement-*.prompt.md` + + * A newly generated prompt file that will create a `.copilot-tracking/changes/*-changes.md` file and proceed to implement the changes. + + Continue to use `task-planner` to iterate on the plan until you have exactly what you want done to your codebase. + + # Planning Instructions + - path: instructions/task-implementation.instructions.md + kind: instruction + usage: | + Continue to use `task-planner` to iterate on the plan until you have exactly what you want done to your codebase. + + When you are ready to implement the plan, **create a new chat** and switch to `Agent` mode then fire off the newly generated prompt. + + ```markdown, implement-fabric-rti-changes.prompt.md + --- + mode: agent + title: Implement microsoft fabric realtime intelligence terraform support + --- + /implement-fabric-rti-blueprint-modification phaseStop=true + ``` + + This prompt has the added benefit of attaching the plan as instructions, which helps with keeping the plan in context throughout the whole conversation. + + **Expert Warning** ->>Use `phaseStop=false` to have Copilot implement the whole plan without stopping. Additionally, you can use `taskStop=true` to have Copilot stop after every Task implementation for finer detail control. + + To use these generated instructions and prompts, you'll need to update your `settings.json` accordingly: + + ```json + "chat.instructionsFilesLocations": { + // Existing instructions folders... + ".copilot-tracking/plans": true + }, + "chat.promptFilesLocations": { + // Existing prompts folders... + ".copilot-tracking/prompts": true + }, + ``` + +display: + ordering: alpha # or "manual" to preserve the order above + show_badge: false # set to true to show collection badge on items diff --git a/collections/edge-ai-tasks.md b/collections/edge-ai-tasks.md new file mode 100644 index 0000000..75559ca --- /dev/null +++ b/collections/edge-ai-tasks.md @@ -0,0 +1,100 @@ +# Tasks by microsoft/edge-ai + +Task Researcher and Task Planner for intermediate to expert users and large codebases - Brought to you by microsoft/edge-ai + +**Tags:** architecture, planning, research, tasks, implementation + +## Items in this Collection + +| Title | Type | Description | +| ----- | ---- | ----------- | +| [Task Researcher Instructions](../chatmodes/task-researcher.chatmode.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/chatmode?url=vscode%3Achat-mode%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fchatmodes%2Ftask-researcher.chatmode.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/chatmode?url=vscode-insiders%3Achat-mode%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fchatmodes%2Ftask-researcher.chatmode.md) | Chat Mode | Task research specialist for comprehensive project analysis - Brought to you by microsoft/edge-ai [see usage](#task-researcher-instructions) | +| [Task Planner Instructions](../chatmodes/task-planner.chatmode.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/chatmode?url=vscode%3Achat-mode%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fchatmodes%2Ftask-planner.chatmode.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/chatmode?url=vscode-insiders%3Achat-mode%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fchatmodes%2Ftask-planner.chatmode.md) | Chat Mode | Task planner for creating actionable implementation plans - Brought to you by microsoft/edge-ai [see usage](#task-planner-instructions) | +| [Task Plan Implementation Instructions](../instructions/task-implementation.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Ftask-implementation.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Ftask-implementation.instructions.md) | Instruction | Instructions for implementing task plans with progressive tracking and change record - Brought to you by microsoft/edge-ai [see usage](#task-plan-implementation-instructions) | + +## Collection Usage + +### Task Researcher Instructions + +Now you can iterate on research for your tasks! + +```markdown, research.prompt.md +--- +mode: task-researcher +title: Research microsoft fabric realtime intelligence terraform support +--- +Review the microsoft documentation for fabric realtime intelligence +and come up with ideas on how to implement this support into our terraform components. +``` + +Research is dumped out into a .copilot-tracking/research/*-research.md file and will include discoveries for GHCP along with examples and schema that will be useful during implementation. + +Also, task-researcher will provide additional ideas for implementation which you can work with GitHub Copilot on selecting the right one to focus on. + +--- + +### Task Planner Instructions + +Also, task-researcher will provide additional ideas for implementation which you can work with GitHub Copilot on selecting the right one to focus on. + +```markdown, task-plan.prompt.md +--- +mode: task-planner +title: Plan microsoft fabric realtime intelligence terraform support +--- +#file: .copilot-tracking/research/*-fabric-rti-blueprint-modification-research.md +Build a plan to support adding fabric rti to this project +``` + +`task-planner` will help you create a plan for implementing your task(s). It will use your fully researched ideas or build new research if not already provided. + +`task-planner` will produce three (3) files that will be used by `task-implementation.instructions.md`. + +* `.copilot-tracking/plan/*-plan.instructions.md` + + * A newly generated instructions file that has the plan as a checklist of Phases and Tasks. +* `.copilot-tracking/details/*-details.md` + + * The details for the implementation, the plan file refers to this file for specific details (important if you have a big plan). +* `.copilot-tracking/prompts/implement-*.prompt.md` + + * A newly generated prompt file that will create a `.copilot-tracking/changes/*-changes.md` file and proceed to implement the changes. + +Continue to use `task-planner` to iterate on the plan until you have exactly what you want done to your codebase. + +--- + +### Task Plan Implementation Instructions + +Continue to use `task-planner` to iterate on the plan until you have exactly what you want done to your codebase. + +When you are ready to implement the plan, **create a new chat** and switch to `Agent` mode then fire off the newly generated prompt. + +```markdown, implement-fabric-rti-changes.prompt.md +--- +mode: agent +title: Implement microsoft fabric realtime intelligence terraform support +--- +/implement-fabric-rti-blueprint-modification phaseStop=true +``` + +This prompt has the added benefit of attaching the plan as instructions, which helps with keeping the plan in context throughout the whole conversation. + +**Expert Warning** ->>Use `phaseStop=false` to have Copilot implement the whole plan without stopping. Additionally, you can use `taskStop=true` to have Copilot stop after every Task implementation for finer detail control. + +To use these generated instructions and prompts, you'll need to update your `settings.json` accordingly: + +```json + "chat.instructionsFilesLocations": { + // Existing instructions folders... + ".copilot-tracking/plans": true + }, + "chat.promptFilesLocations": { + // Existing prompts folders... + ".copilot-tracking/prompts": true + }, +``` + +--- + +*This collection includes 3 curated items for tasks by microsoft/edge-ai.* \ No newline at end of file diff --git a/update-readme.js b/update-readme.js index 2c71dba..d6ce2da 100755 --- a/update-readme.js +++ b/update-readme.js @@ -142,7 +142,10 @@ function extractTitle(filePath) { // Track code blocks to ignore headings inside them if (frontmatterEnded2) { - if (line.trim().startsWith("```") || line.trim().startsWith("````")) { + if ( + line.trim().startsWith("```") || + line.trim().startsWith("````") + ) { inCodeBlock = !inCodeBlock; continue; } @@ -156,7 +159,10 @@ function extractTitle(filePath) { // No frontmatter, look for first heading (but not in code blocks) let inCodeBlock = false; for (const line of lines) { - if (line.trim().startsWith("```") || line.trim().startsWith("````")) { + if ( + line.trim().startsWith("```") || + line.trim().startsWith("````") + ) { inCodeBlock = !inCodeBlock; continue; } @@ -260,7 +266,9 @@ function extractDescription(filePath) { let description = descriptionMatch[1]; // Check if the description is wrapped in single quotes and handle escaped quotes - const singleQuoteMatch = line.match(/^description:\s*'(.+?)'\s*$/); + const singleQuoteMatch = line.match( + /^description:\s*'(.+?)'\s*$/ + ); if (singleQuoteMatch) { // Replace escaped single quotes ('') with single quotes (') description = singleQuoteMatch[1].replace(/''/g, "'"); @@ -308,8 +316,12 @@ const AKA_INSTALL_URLS = { function makeBadges(link, type) { const aka = AKA_INSTALL_URLS[type] || AKA_INSTALL_URLS.instructions; - const vscodeUrl = `${aka}?url=${encodeURIComponent(`vscode:chat-${type}/install?url=${repoBaseUrl}/${link}`)}`; - const insidersUrl = `${aka}?url=${encodeURIComponent(`vscode-insiders:chat-${type}/install?url=${repoBaseUrl}/${link}`)}`; + const vscodeUrl = `${aka}?url=${encodeURIComponent( + `vscode:chat-${type}/install?url=${repoBaseUrl}/${link}` + )}`; + const insidersUrl = `${aka}?url=${encodeURIComponent( + `vscode-insiders:chat-${type}/install?url=${repoBaseUrl}/${link}` + )}`; return `[![Install in VS Code](${vscodeInstallImage})](${vscodeUrl})
[![Install in VS Code Insiders](${vscodeInsidersInstallImage})](${insidersUrl})`; } @@ -405,8 +417,7 @@ function generatePromptsSection(promptsDir) { } // Create table header - let promptsContent = - "| Title | Description |\n| ----- | ----------- |\n"; + let promptsContent = "| Title | Description |\n| ----- | ----------- |\n"; // Generate table rows for each prompt file for (const entry of promptEntries) { @@ -462,8 +473,7 @@ function generateChatModesSection(chatmodesDir) { } // Create table header - let chatmodesContent = - "| Title | Description |\n| ----- | ----------- |\n"; + let chatmodesContent = "| Title | Description |\n| ----- | ----------- |\n"; // Generate table rows for each chat mode file for (const entry of chatmodeEntries) { @@ -502,19 +512,22 @@ function generateCollectionsSection(collectionsDir) { .filter((file) => file.endsWith(".collection.yml")); // Map collection files to objects with name for sorting - const collectionEntries = collectionFiles.map((file) => { - const filePath = path.join(collectionsDir, file); - const collection = parseCollectionYaml(filePath); + const collectionEntries = collectionFiles + .map((file) => { + const filePath = path.join(collectionsDir, file); + const collection = parseCollectionYaml(filePath); - if (!collection) { - console.warn(`Failed to parse collection: ${file}`); - return null; - } + if (!collection) { + console.warn(`Failed to parse collection: ${file}`); + return null; + } - const collectionId = collection.id || path.basename(file, ".collection.yml"); - const name = collection.name || collectionId; - return { file, filePath, collection, collectionId, name }; - }).filter(entry => entry !== null); // Remove failed parses + const collectionId = + collection.id || path.basename(file, ".collection.yml"); + const name = collection.name || collectionId; + return { file, filePath, collection, collectionId, name }; + }) + .filter((entry) => entry !== null); // Remove failed parses // Sort by name alphabetically collectionEntries.sort((a, b) => a.name.localeCompare(b.name)); @@ -566,6 +579,9 @@ function generateCollectionReadme(collection, collectionId) { content += `## Items in this Collection\n\n`; content += `| Title | Type | Description |\n| ----- | ---- | ----------- |\n`; + let collectionUsageHeader = "## Collection Usage\n\n"; + let collectionUsageContent = []; + // Sort items based on display.ordering setting const items = [...collection.items]; if (collection.display?.ordering === "alpha") { @@ -580,19 +596,52 @@ function generateCollectionReadme(collection, collectionId) { const filePath = path.join(__dirname, item.path); const title = extractTitle(filePath); const description = extractDescription(filePath) || "No description"; - const typeDisplay = item.kind === "chat-mode" ? "Chat Mode" : - item.kind === "instruction" ? "Instruction" : "Prompt"; + + const typeDisplay = + item.kind === "chat-mode" + ? "Chat Mode" + : item.kind === "instruction" + ? "Instruction" + : "Prompt"; const link = `../${item.path}`; // Create install badges for each item - const badges = makeBadges(item.path, item.kind === "instruction" ? "instructions" : - item.kind === "chat-mode" ? "mode" : "prompt"); + const badges = makeBadges( + item.path, + item.kind === "instruction" + ? "instructions" + : item.kind === "chat-mode" + ? "mode" + : "prompt" + ); - content += `| [${title}](${link})
${badges} | ${typeDisplay} | ${description} |\n`; + const usageDescription = item.usage + ? `${description} [see usage](#${title + .replace(/\s+/g, "-") + .toLowerCase()})` + : description; + + content += `| [${title}](${link})
${badges} | ${typeDisplay} | ${usageDescription} |\n`; + // Generate Usage section for each collection + if (item.usage && item.usage.trim()) { + collectionUsageContent.push( + `### ${title}\n\n${item.usage.trim()}\n\n---\n\n` + ); + } } + // Append the usage section if any items had usage defined + if (collectionUsageContent.length > 0) { + content += `\n${collectionUsageHeader}${collectionUsageContent.join("")}`; + } else if (collection.display?.show_badge) { + content += "\n---\n"; + } + + // Optional badge note at the end if show_badge is true if (collection.display?.show_badge) { - content += `\n---\n*This collection includes ${items.length} curated items for ${name.toLowerCase()}.*`; + content += `*This collection includes ${ + items.length + } curated items for ${name.toLowerCase()}.*`; } return content; @@ -604,12 +653,16 @@ function writeFileIfChanged(filePath, content) { if (exists) { const original = fs.readFileSync(filePath, "utf8"); if (original === content) { - console.log(`${path.basename(filePath)} is already up to date. No changes needed.`); + console.log( + `${path.basename(filePath)} is already up to date. No changes needed.` + ); return; } } fs.writeFileSync(filePath, content); - console.log(`${path.basename(filePath)} ${exists ? "updated" : "created"} successfully!`); + console.log( + `${path.basename(filePath)} ${exists ? "updated" : "created"} successfully!` + ); } // Build per-category README content using existing generators, upgrading headings to H1 @@ -633,10 +686,16 @@ try { const collectionsDir = path.join(__dirname, "collections"); // Compose headers for standalone files by converting section headers to H1 - const instructionsHeader = TEMPLATES.instructionsSection.replace(/^##\s/m, "# "); + const instructionsHeader = TEMPLATES.instructionsSection.replace( + /^##\s/m, + "# " + ); const promptsHeader = TEMPLATES.promptsSection.replace(/^##\s/m, "# "); const chatmodesHeader = TEMPLATES.chatmodesSection.replace(/^##\s/m, "# "); - const collectionsHeader = TEMPLATES.collectionsSection.replace(/^##\s/m, "# "); + const collectionsHeader = TEMPLATES.collectionsSection.replace( + /^##\s/m, + "# " + ); const instructionsReadme = buildCategoryReadme( generateInstructionsSection, @@ -666,10 +725,19 @@ try { ); // Write category outputs - writeFileIfChanged(path.join(__dirname, "README.instructions.md"), instructionsReadme); + writeFileIfChanged( + path.join(__dirname, "README.instructions.md"), + instructionsReadme + ); writeFileIfChanged(path.join(__dirname, "README.prompts.md"), promptsReadme); - writeFileIfChanged(path.join(__dirname, "README.chatmodes.md"), chatmodesReadme); - writeFileIfChanged(path.join(__dirname, "README.collections.md"), collectionsReadme); + writeFileIfChanged( + path.join(__dirname, "README.chatmodes.md"), + chatmodesReadme + ); + writeFileIfChanged( + path.join(__dirname, "README.collections.md"), + collectionsReadme + ); // Generate individual collection README files if (fs.existsSync(collectionsDir)) { @@ -684,8 +752,12 @@ try { const collection = parseCollectionYaml(filePath); if (collection) { - const collectionId = collection.id || path.basename(file, ".collection.yml"); - const readmeContent = generateCollectionReadme(collection, collectionId); + const collectionId = + collection.id || path.basename(file, ".collection.yml"); + const readmeContent = generateCollectionReadme( + collection, + collectionId + ); const readmeFile = path.join(collectionsDir, `${collectionId}.md`); writeFileIfChanged(readmeFile, readmeContent); } diff --git a/yaml-parser.js b/yaml-parser.js index b209908..40ede78 100644 --- a/yaml-parser.js +++ b/yaml-parser.js @@ -20,14 +20,53 @@ function parseCollectionYaml(filePath) { let currentArray = null; let currentObject = null; + const readLiteralBlock = (startIndex, parentIndent) => { + const blockLines = []; + let blockIndent = null; + let index = startIndex; + + for (; index < lines.length; index++) { + const rawLine = lines[index]; + const trimmedLine = rawLine.trimEnd(); + const contentOnly = trimmedLine.trim(); + const lineIndent = rawLine.length - rawLine.trimLeft().length; + + if (contentOnly === "" && blockIndent === null) { + // Preserve leading blank lines inside the literal block + blockLines.push(""); + continue; + } + + if (contentOnly !== "" && lineIndent <= parentIndent) { + break; + } + + if (contentOnly === "") { + blockLines.push(""); + continue; + } + + if (blockIndent === null) { + blockIndent = lineIndent; + } + + blockLines.push(rawLine.slice(blockIndent)); + } + + return { + content: blockLines.join("\n").replace(/\r/g, "").trimEnd(), + nextIndex: index - 1, + }; + }; + for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmed = line.trim(); - + if (!trimmed || trimmed.startsWith("#")) continue; const leadingSpaces = line.length - line.trimLeft().length; - + // Handle array items starting with - if (trimmed.startsWith("- ")) { if (currentKey === "items") { @@ -35,12 +74,12 @@ function parseCollectionYaml(filePath) { currentArray = []; result[currentKey] = currentArray; } - + // Parse item object const item = {}; currentArray.push(item); currentObject = item; - + // Handle inline properties on same line as - const restOfLine = trimmed.substring(2).trim(); if (restOfLine) { @@ -65,13 +104,13 @@ function parseCollectionYaml(filePath) { const colonIndex = trimmed.indexOf(":"); const key = trimmed.substring(0, colonIndex).trim(); let value = trimmed.substring(colonIndex + 1).trim(); - + if (leadingSpaces === 0) { // Top-level property currentKey = key; currentArray = null; currentObject = null; - + if (value) { // Handle array format [item1, item2, item3] if (value.startsWith("[") && value.endsWith("]")) { @@ -82,6 +121,10 @@ function parseCollectionYaml(filePath) { result[key] = []; } currentKey = null; // Reset since we handled the array + } else if (value === "|" || value === ">") { + const { content: blockContent, nextIndex } = readLiteralBlock(i + 1, leadingSpaces); + result[key] = blockContent; + i = nextIndex; } else { result[key] = value; } @@ -95,14 +138,26 @@ function parseCollectionYaml(filePath) { } } else if (currentObject && leadingSpaces > 0) { // Property of current object (e.g., display properties) - currentObject[key] = value === "true" ? true : value === "false" ? false : value; + if (value === "|" || value === ">") { + const { content: blockContent, nextIndex } = readLiteralBlock(i + 1, leadingSpaces); + currentObject[key] = blockContent; + i = nextIndex; + } else { + currentObject[key] = value === "true" ? true : value === "false" ? false : value; + } } else if (currentArray && currentObject && leadingSpaces > 2) { // Property of array item object - currentObject[key] = value; + if (value === "|" || value === ">") { + const { content: blockContent, nextIndex } = readLiteralBlock(i + 1, leadingSpaces); + currentObject[key] = blockContent; + i = nextIndex; + } else { + currentObject[key] = value; + } } } } - + return result; }, filePath, @@ -110,4 +165,4 @@ function parseCollectionYaml(filePath) { ); } -module.exports = { parseCollectionYaml, safeFileOperation }; \ No newline at end of file +module.exports = { parseCollectionYaml, safeFileOperation };