Skip to content

Commit 6e18e90

Browse files
authored
ContentType prompt for AI tools (#58979)
1 parent 4c04b11 commit 6e18e90

File tree

5 files changed

+183
-16
lines changed

5 files changed

+183
-16
lines changed

src/ai-tools/lib/file-utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export function mergeFrontmatterProperties(filePath: string, newPropertiesYaml:
8585
)
8686
}
8787

88-
if (!parsed.content) {
88+
if (parsed.content === undefined || parsed.content === null) {
8989
throw new Error('Failed to parse content from file')
9090
}
9191

@@ -133,9 +133,11 @@ export function mergeFrontmatterProperties(filePath: string, newPropertiesYaml:
133133
const formattedValue = typeof value === 'string' ? `'${value.replace(/'/g, "''")}'` : value
134134

135135
// Find the line with this field
136+
let foundField = false
136137
for (let i = 1; i < frontmatterEndIndex; i++) {
137138
const line = lines[i]
138139
if (line.startsWith(`${key}:`)) {
140+
foundField = true
139141
// Simple replacement: keep the field name and spacing, replace the value
140142
const colonIndex = line.indexOf(':')
141143
const leadingSpace = line.substring(colonIndex + 1, colonIndex + 2) // Usually a space
@@ -150,6 +152,12 @@ export function mergeFrontmatterProperties(filePath: string, newPropertiesYaml:
150152
break
151153
}
152154
}
155+
156+
// If field doesn't exist, add it before the closing ---
157+
if (!foundField && frontmatterEndIndex > 0) {
158+
lines.splice(frontmatterEndIndex, 0, `${key}: ${formattedValue}`)
159+
frontmatterEndIndex++
160+
}
153161
}
154162

155163
return lines.join('\n')

src/ai-tools/lib/prompt-utils.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { fileURLToPath } from 'url'
22
import fs from 'fs'
33
import yaml from 'js-yaml'
44
import path from 'path'
5+
import readFrontmatter from '@/frame/lib/read-frontmatter'
56
import { callModelsApi } from '@/ai-tools/lib/call-models-api'
67

78
export interface PromptMessage {
@@ -52,6 +53,67 @@ export function getRefinementDescriptions(editorTypes: string[]): string {
5253
return editorTypes.join(', ')
5354
}
5455

56+
/**
57+
* Enrich context for intro prompt on index.md files
58+
*/
59+
export function enrichIndexContext(filePath: string, content: string): string {
60+
if (!filePath.endsWith('index.md')) return content
61+
62+
try {
63+
const { data } = readFrontmatter(content)
64+
if (!data) return content
65+
66+
// Extract product name from file path (e.g., content/github-models/ -> "GitHub Models")
67+
const productMatch = filePath.replace(/\\/g, '/').match(/content\/([^/]+)/)
68+
const productName = productMatch
69+
? productMatch[1]
70+
.split('-')
71+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
72+
.join(' ')
73+
: ''
74+
75+
// Get child article titles
76+
const titles: string[] = []
77+
if (data.children && Array.isArray(data.children)) {
78+
const dir = path.dirname(filePath)
79+
for (const childPath of data.children.slice(0, 20)) {
80+
try {
81+
const childFile = path.join(dir, `${childPath.replace(/^\//, '')}.md`)
82+
const childContent = fs.readFileSync(childFile, 'utf8')
83+
const { data: childData } = readFrontmatter(childContent)
84+
if (childData?.title) titles.push(childData.title)
85+
} catch (error) {
86+
if (process.env.AI_TOOLS_VERBOSE === 'true') {
87+
console.warn('Failed to read or parse child article for intro context:', {
88+
filePath,
89+
childPath,
90+
error,
91+
})
92+
}
93+
}
94+
}
95+
}
96+
97+
// Build context note
98+
const parts: string[] = []
99+
if (productName) parts.push(`Product: ${productName}`)
100+
if (titles.length > 0) parts.push(`Child articles: ${titles.join(', ')}`)
101+
102+
if (parts.length > 0) {
103+
return `\n\n---\nContext for intro generation:\n${parts.join('\n')}\n---\n\n${content}`
104+
}
105+
} catch (error) {
106+
if (process.env.AI_TOOLS_VERBOSE === 'true') {
107+
console.warn('Failed to enrich index context for intro generation:', {
108+
filePath,
109+
error,
110+
})
111+
}
112+
}
113+
114+
return content
115+
}
116+
55117
/**
56118
* Call an editor with the given content and options
57119
*/
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Your job is to read through GitHub Docs articles that I provide and figure out what content type it _most_ aligns to and add the frontmatter property `contentType` with an appropriate value.
2+
3+
**Available `contentType` values (MUST choose from this exact list):**
4+
5+
- 'get-started' (MANDATORY for files with "quickstart" in the filename; also use for other getting started content)
6+
- 'concepts' (use for files with "about" in the filename; also use for other conceptual content)
7+
- 'how-tos' (use for procedural content AND for subdirectory index.md files that have a `children` array)
8+
- 'rai' (optional - only applies to files with "responsible-use" or "rai" in the filenames)
9+
- 'reference'
10+
- 'tutorials'
11+
12+
There is one additional type, 'landing', which can ONLY be used on top-level product index.md files: 'content/<product>/index.md'
13+
14+
**CRITICAL RULE**: If a file is an index.md with MORE than three directory parts (e.g., 'content/<product>/<subdirectory>/index.md'), it is a subdirectory index and should use 'how-tos', NOT 'landing'. The fact that it has a `children` array does NOT make it a landing page.
15+
16+
For prior art, see the following file sets:
17+
18+
- content/copilot/
19+
- content/actions/
20+
- content/account-and-profile/
21+
- content/integrations/
22+
23+
## Output format
24+
25+
**Important:** Output ONLY the new frontmatter property that should be added to the file. Do not output the entire file content.
26+
27+
```yaml
28+
contentType: [selected option]
29+
```
30+
31+
<!-- IF_WRITE_MODE -->
32+
**CRITICAL**: You are in write mode. Output ONLY the YAML frontmatter properties to update.
33+
- Return just the YAML property in the format above
34+
- Do NOT include analysis, explanations, or formatting
35+
- Do NOT wrap in markdown code blocks or ```yaml
36+
- Do NOT include the analysis format
37+
- Just return the clean YAML properties for merging
38+
<!-- END_WRITE_MODE -->

src/ai-tools/prompts/intro.md

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
You are an expert SEO content optimizer specializing in GitHub documentation.
2-
Your task is to analyze a GitHub Docs content file and generate or optimize
1+
You are an expert SEO content optimizer specializing in GitHub documentation.
2+
Your task is to analyze a GitHub Docs content file and generate or optimize
33
the intro frontmatter property following Google's meta description best practices.
44

5+
## Context for index.md files
6+
7+
For index.md files, you will receive additional context about the product and child articles:
8+
- Product name (e.g., "GitHub Models", "GitHub Copilot")
9+
- List of child article titles
10+
11+
Use this context to create specific, product-focused intros rather than generic guidance.
12+
13+
**Examples of good vs generic intros:**
14+
- ❌ "Explore tutorials to build projects and learn new skills with GitHub"
15+
- ❌ "Learn practical guides and step-by-step instructions"
16+
- ✅ "Build AI applications with GitHub Models through hands-on tutorials covering model evaluation and deployment"
17+
518
## Core Requirements
619

720
**Primary constraints (must-haves):**
@@ -11,6 +24,11 @@ the intro frontmatter property following Google's meta description best practice
1124
* Different approach than title - don't start with same words/phrases
1225
* Lists 2-3 concrete outcomes maximum
1326

27+
**For index.md files:**
28+
* Use the provided product name and child article context to create specific intros
29+
* Identify key themes from child article titles to highlight covered topics
30+
* Make intro specific to the product and topics, not generic guidance
31+
1432
**Secondary optimizations (nice-to-haves):**
1533
* Include relevant keywords naturally
1634
* Version-agnostic ({% data variables.* %} OK, avoid {% ifversion %})
@@ -46,6 +64,15 @@ the intro frontmatter property following Google's meta description best practice
4664
**Uniqueness**: Different angle from article title
4765
**Simplicity**: No colons, no complex lists, flowing sentences
4866

67+
**Examples for index.md files:**
68+
69+
**Too generic** (ignores provided context):
70+
- Bad: "Explore practical guides and step-by-step instructions to accomplish tasks and solve problems on GitHub"
71+
72+
**Product-specific** (uses provided context):
73+
- Better: "Learn to use GitHub Models for prototyping, evaluate AI models, and scale deployments across your organization"
74+
- Or: "Build AI-powered applications with GitHub Models, from initial testing to enterprise-scale deployment"
75+
4976
## Output format
5077

5178
Use plain text formatting optimized for terminal readability:
@@ -63,23 +90,23 @@ SEO-friendly alternative: "[Single, concise intro that summarizes the article's
6390
## Character limits by content type
6491

6592
- **Articles**: Maximum 354 characters
66-
- **Categories**: Maximum 362 characters
93+
- **Categories**: Maximum 362 characters
6794
- **Map Topics**: Maximum 362 characters
6895

6996
## Liquid syntax guidelines
7097

71-
**Keep these in intros** (they're acceptable for dynamic content):
72-
- {% data variables.* %} - Product names and variables
73-
- {% data reusables.* %} - Reusable content blocks
98+
**When creating intros from scratch** (no existing intro field):
99+
- Use plain text only - DO NOT use {% data variables.* %} or {% data reusables.* %} syntax
100+
- Write out product names in full (e.g., "GitHub Copilot", "GitHub Actions", "GitHub Docs")
101+
- This prevents hallucinating incorrect variable names
74102

75-
**Avoid these in intros** (version-agnostic content preferred):
76-
- {% ifversion %} blocks - Create intros that work across all supported versions
103+
**When updating existing intros** (intro field already exists):
104+
- Preserve any existing {% data variables.* %} and {% data reusables.* %} references
105+
- You may use the same variable patterns that already appear in the existing intro
106+
- Do not introduce new variable references that weren't in the original
77107

78-
**Common variable meanings** (for analysis purposes):
79-
- {% data variables.product.prodname_github %} = "GitHub"
80-
- {% data variables.product.prodname_ghe_server %} = "GitHub Enterprise Server"
81-
- {% data variables.product.prodname_copilot %} = "GitHub Copilot"
82-
- {% data variables.copilot.copilot_coding_agent %} = "Copilot Coding Agent"
108+
**Always avoid**:
109+
- {% ifversion %} blocks - Create intros that work across all supported versions
83110

84111
Focus on creating intros that would make sense to someone discovering this content through Google search, clearly communicating the value and relevance of the article.
85112

@@ -89,6 +116,12 @@ Focus on creating intros that would make sense to someone discovering this conte
89116

90117
**CRITICAL**: You are in write mode. Output ONLY the YAML frontmatter property to update.
91118

119+
**For index.md files:**
120+
- Use the provided product name and child article context in your intro
121+
- Do NOT write generic intros that could apply to any product
122+
- Make the intro specific to the actual product and covered topics
123+
124+
**Output format:**
92125
- Return just: `intro: "your improved intro text"`
93126
- Do NOT include analysis, scoring, explanations, or formatting
94127
- Do NOT wrap in markdown code blocks or ```yaml

src/ai-tools/scripts/ai-tools.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import path from 'path'
44
import ora from 'ora'
55
import { execFileSync } from 'child_process'
66
import dotenv from 'dotenv'
7+
import readFrontmatter from '@/frame/lib/read-frontmatter'
78
import { findMarkdownFiles, mergeFrontmatterProperties } from '@/ai-tools/lib/file-utils'
89
import {
910
getPromptsDir,
1011
getAvailableEditorTypes,
1112
getRefinementDescriptions,
1213
callEditor,
14+
enrichIndexContext,
1315
} from '@/ai-tools/lib/prompt-utils'
1416
import { fetchCopilotSpace, convertSpaceToPrompt } from '@/ai-tools/lib/spaces-utils'
1517
import { ensureGitHubToken } from '@/ai-tools/lib/auth-utils'
@@ -196,12 +198,36 @@ program
196198
spinner.text = `Processing: ${relativePath}`
197199
try {
198200
// Expand Liquid references before processing
201+
let originalIntro = ''
202+
if (editorType === 'intro') {
203+
const originalContent = fs.readFileSync(fileToProcess, 'utf8')
204+
const { data: originalData } = readFrontmatter(originalContent)
205+
originalIntro = originalData?.intro || ''
206+
}
207+
199208
if (options.verbose) {
200209
console.log(`Expanding Liquid references in: ${relativePath}`)
201210
}
202211
runLiquidTagsScript('expand', [fileToProcess], options.verbose || false)
203212

204-
const content = fs.readFileSync(fileToProcess, 'utf8')
213+
let content = fs.readFileSync(fileToProcess, 'utf8')
214+
215+
// For intro prompt, add original intro and enrich context
216+
if (editorType === 'intro') {
217+
if (originalIntro) {
218+
content = `\n\n---\nOriginal intro (unresolved): ${originalIntro}\n---\n\n${content}`
219+
}
220+
content = enrichIndexContext(fileToProcess, content)
221+
}
222+
223+
// For content-type prompt, skip files that already have contentType
224+
if (editorType === 'content-type' && content.includes('contentType:')) {
225+
spinner.stop()
226+
console.log(`⏭️ Skipping ${relativePath} (already has contentType)`)
227+
runLiquidTagsScript('restore', [fileToProcess], false)
228+
continue
229+
}
230+
205231
const answer = await callEditor(
206232
editorType,
207233
content,
@@ -213,7 +239,7 @@ program
213239
spinner.stop()
214240

215241
if (options.write) {
216-
if (editorType === 'intro') {
242+
if (editorType === 'intro' || editorType === 'content-type') {
217243
// For frontmatter addition/modification, merge properties instead of overwriting entire file
218244
const updatedContent = mergeFrontmatterProperties(fileToProcess, answer)
219245
fs.writeFileSync(fileToProcess, updatedContent, 'utf8')

0 commit comments

Comments
 (0)