diff --git a/package.json b/package.json index 121adffe67..3382430511 100644 --- a/package.json +++ b/package.json @@ -4351,6 +4351,7 @@ "@shikijs/monaco": "^3.7.0", "@types/chai": "^4.1.4", "@types/glob": "7.1.3", + "@types/js-yaml": "^4.0.9", "@types/lru-cache": "^5.1.0", "@types/marked": "^0.7.2", "@types/mocha": "^8.2.2", @@ -4432,6 +4433,7 @@ "debounce": "^1.2.1", "events": "3.2.0", "fast-deep-equal": "^3.1.3", + "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "jszip": "^3.10.1", "lru-cache": "6.0.0", @@ -4452,4 +4454,4 @@ "string_decoder": "^1.3.0" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 9dd9002c84..b32a89272f 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -1346,10 +1346,13 @@ export class FolderRepositoryManager extends Disposable { } async getIssueTemplates(): Promise { - const pattern = '{docs,.github}/ISSUE_TEMPLATE/*.md'; - return vscode.workspace.findFiles( - new vscode.RelativePattern(this._repository.rootUri, pattern), null - ); + const mdPattern = '{docs,.github}/ISSUE_TEMPLATE/*.md'; + const ymlPattern = '{docs,.github}/ISSUE_TEMPLATE/*.yml'; + const [mdTemplates, ymlTemplates] = await Promise.all([ + vscode.workspace.findFiles(new vscode.RelativePattern(this._repository.rootUri, mdPattern), null), + vscode.workspace.findFiles(new vscode.RelativePattern(this._repository.rootUri, ymlPattern), null) + ]); + return [...mdTemplates, ...ymlTemplates]; } async getPullRequestTemplateBody(owner: string): Promise { diff --git a/src/issues/issueFeatureRegistrar.ts b/src/issues/issueFeatureRegistrar.ts index a4f253a57d..e3cc9dfec1 100644 --- a/src/issues/issueFeatureRegistrar.ts +++ b/src/issues/issueFeatureRegistrar.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { basename } from 'path'; +import * as yaml from 'js-yaml'; import * as vscode from 'vscode'; import { CurrentIssue } from './currentIssue'; import { IssueCompletionProvider } from './issueCompletionProvider'; @@ -56,6 +57,7 @@ import { PermalinkInfo, pushAndCreatePR, USER_EXPRESSION, + YamlIssueTemplate, } from './util'; import { truncate } from '../common/utils'; import { OctokitCommon } from '../github/common'; @@ -1250,6 +1252,19 @@ ${options?.body ?? ''}\n } private getDataFromTemplate(template: string): IssueTemplate { + // Try to parse as YAML first (YAML templates have a different structure) + try { + const parsed = yaml.load(template); + // Check if it looks like a YAML issue template (has name and body fields) + if (parsed && typeof parsed === 'object' && (parsed as YamlIssueTemplate).name && (parsed as YamlIssueTemplate).body) { + // This is a YAML template + return this.parseYamlTemplate(parsed as YamlIssueTemplate); + } + } catch (e) { + // Not a valid YAML, continue to Markdown parsing + } + + // Parse as Markdown frontmatter template const title = template.match(/title:\s*(.*)/)?.[1]?.replace(/^["']|["']$/g, ''); const name = template.match(/name:\s*(.*)/)?.[1]?.replace(/^["']|["']$/g, ''); const about = template.match(/about:\s*(.*)/)?.[1]?.replace(/^["']|["']$/g, ''); @@ -1257,6 +1272,58 @@ ${options?.body ?? ''}\n return { title, name, about, body }; } + private parseYamlTemplate(parsed: YamlIssueTemplate): IssueTemplate { + const name = parsed.name; + const about = parsed.description || parsed.about; + const title = parsed.title; + + // Convert YAML body fields to markdown + let body = ''; + if (parsed.body && Array.isArray(parsed.body)) { + for (const field of parsed.body) { + if (field.type === 'markdown' && field.attributes?.value) { + body += field.attributes.value + '\n\n'; + } else if (field.type === 'textarea' && field.attributes?.label) { + body += `## ${field.attributes.label}\n\n`; + if (field.attributes.description) { + body += `${field.attributes.description}\n\n`; + } + if (field.attributes.placeholder) { + body += `${field.attributes.placeholder}\n\n`; + } else if (field.attributes.value) { + body += `${field.attributes.value}\n\n`; + } + } else if (field.type === 'input' && field.attributes?.label) { + body += `## ${field.attributes.label}\n\n`; + if (field.attributes.description) { + body += `${field.attributes.description}\n\n`; + } + if (field.attributes.placeholder) { + body += `${field.attributes.placeholder}\n\n`; + } + } else if (field.type === 'dropdown' && field.attributes?.label) { + body += `## ${field.attributes.label}\n\n`; + if (field.attributes.description) { + body += `${field.attributes.description}\n\n`; + } + if (field.attributes.options && Array.isArray(field.attributes.options)) { + body += field.attributes.options.map((opt: string | { label?: string }) => typeof opt === 'string' ? `- ${opt}` : `- ${opt.label || ''}`).join('\n') + '\n\n'; + } + } else if (field.type === 'checkboxes' && field.attributes?.label) { + body += `## ${field.attributes.label}\n\n`; + if (field.attributes.description) { + body += `${field.attributes.description}\n\n`; + } + if (field.attributes.options && Array.isArray(field.attributes.options)) { + body += field.attributes.options.map((opt: { label?: string } | string) => `- [ ] ${typeof opt === 'string' ? opt : opt.label || ''}`).join('\n') + '\n\n'; + } + } + } + } + + return { title, name, about, body: body.trim() || undefined }; + } + private async doCreateIssue( document: vscode.TextDocument | undefined, newIssue: NewIssue | undefined, diff --git a/src/issues/util.ts b/src/issues/util.ts index 87a4e7e382..39a82e5709 100644 --- a/src/issues/util.ts +++ b/src/issues/util.ts @@ -94,6 +94,31 @@ export interface IssueTemplate { body: string | undefined } +export interface YamlIssueTemplate { + name?: string; + description?: string; + about?: string; + title?: string; + labels?: string[]; + assignees?: string[]; + body?: YamlTemplateField[]; +} + +export interface YamlTemplateField { + type: 'markdown' | 'textarea' | 'input' | 'dropdown' | 'checkboxes'; + id?: string; + attributes?: { + label?: string; + description?: string; + placeholder?: string; + value?: string; + options?: (string | { label?: string; required?: boolean })[]; + }; + validations?: { + required?: boolean; + }; +} + const HEAD = 'HEAD'; const UPSTREAM = 1; const UPS = 2; diff --git a/yarn.lock b/yarn.lock index c69c2b7b0f..bfcc228559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -885,6 +885,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/js-yaml@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" + integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -4760,6 +4765,13 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + jsdom-global@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-3.0.2.tgz#6bd299c13b0c4626b2da2c0393cd4385d606acb9"