-
Notifications
You must be signed in to change notification settings - Fork 11.9k
feat(@angular-devkit/schematics): add schematics to generate ai context files. #30763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
b3aef30
to
736c88d
Compare
70ae7ec
to
288dcfd
Compare
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`); | ||
} | ||
|
||
// Keep this file in sync with the one presented here https://angular.dev/ai/develop-with-ai |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should do something automatted or document it on the FW side, since when there is a change there, a PR needs to be opened here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's handle that as a follow-up when we have a better picture what we need to sync (we might have multiple schematics).
288dcfd
to
53d6719
Compare
53d6719
to
7fd0e75
Compare
7fd0e75
to
c94f532
Compare
3dc7023
to
5ae2b77
Compare
packages/schematics/angular/config/files/__rulesName__.template
Outdated
Show resolved
Hide resolved
"x-prompt": "Which type of configuration file would you like to create?", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
} | ||
}, | ||
"tool": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a multi-select prompt?
We could still default to having them all selected. Would need to change the type to a string array though. But it's probably unlikely that everyone would want all available tools when not specifying the option. Especially since the option may not be known to even exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentionned by @alan-agius4 here, the prompt would show for the others options. Or do you have an alternative in mind ?
048a5d7
to
19858e9
Compare
a59bed2
to
75b1fee
Compare
const configOptions: ConfigOptions = { | ||
project: options.name, | ||
type: ConfigType.Ai, | ||
tool: options.aiConfig as unknown as AiTool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why I couldn't get the enums to match. It works fine for the packageManager above for example. (Yes I also tried with removing "none" from the enum here in ng-new).
bbe0c40
to
614ed54
Compare
How does it work if there is already a rule file in the project? Can it merge them into the single file? |
614ed54
to
9b6c275
Compare
@lacolaco Currently it would fail with a "merge" conflict. We plan to address the "update" of those files in a follow-up PR. |
64e512a
to
49a02e0
Compare
@@ -60,11 +61,17 @@ export default function (options: NgNewOptions): Rule { | |||
zoneless: options.zoneless, | |||
}; | |||
|
|||
const configOptions: ConfigOptions = { | |||
project: options.name, | |||
tool: [options.aiConfig as unknown as AiTool], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not exactly sure why this type assertion was necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is due to the “none” value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even without it, the types mismatch. 🤔
49a02e0
to
215b802
Compare
…xt files. * `ng generate ai-config` to prompt support tools. * `ng generate config ai --tool=gemini` to specify the tool. * `ng new --aiConfig=gemini` to create a new project with AI configuration. Supported ai tools: gemini, claude, copilot, windsurf, cursor.
215b802
to
67eed64
Compare
"items": { | ||
"type": "string", | ||
"enum": ["gemini", "copilot", "claude", "cursor", "windsurf"] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Do we need x-prompt
here too to show up as a prompt for just ng new
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to show the prompt here, we'd need to remove the default and run the schematic directly and not conditionally.
const geminiFile: ContextFileInfo = { rulesName: 'GEMINI.md', directory: '.gemini' }; | ||
const copilotFile: ContextFileInfo = { | ||
rulesName: 'copilot-instructions.md', | ||
directory: '.github', | ||
}; | ||
const claudeFile: ContextFileInfo = { rulesName: 'CLAUDE.md', directory: '.claude' }; | ||
const windsurfFile: ContextFileInfo = { | ||
rulesName: 'guidelines.md', | ||
directory: path.join('.windsurf', 'rules'), | ||
}; | ||
|
||
// Cursor file is a bit different, it has a front matter section. | ||
const cursorFile: ContextFileInfo = { | ||
rulesName: 'cursor.mdc', | ||
directory: path.join('.cursor', 'rules'), | ||
frontmatter: `---\ncontext: true\npriority: high\nscope: project\n---`, | ||
}; | ||
|
||
const AI_TOOLS = { | ||
'gemini': geminiFile, | ||
'claude': claudeFile, | ||
'copilot': copilotFile, | ||
'cursor': cursorFile, | ||
'windsurf': windsurfFile, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT, but I think it makes the code more readable.
const geminiFile: ContextFileInfo = { rulesName: 'GEMINI.md', directory: '.gemini' }; | |
const copilotFile: ContextFileInfo = { | |
rulesName: 'copilot-instructions.md', | |
directory: '.github', | |
}; | |
const claudeFile: ContextFileInfo = { rulesName: 'CLAUDE.md', directory: '.claude' }; | |
const windsurfFile: ContextFileInfo = { | |
rulesName: 'guidelines.md', | |
directory: path.join('.windsurf', 'rules'), | |
}; | |
// Cursor file is a bit different, it has a front matter section. | |
const cursorFile: ContextFileInfo = { | |
rulesName: 'cursor.mdc', | |
directory: path.join('.cursor', 'rules'), | |
frontmatter: `---\ncontext: true\npriority: high\nscope: project\n---`, | |
}; | |
const AI_TOOLS = { | |
'gemini': geminiFile, | |
'claude': claudeFile, | |
'copilot': copilotFile, | |
'cursor': cursorFile, | |
'windsurf': windsurfFile, | |
}; | |
const AI_TOOLS = { | |
gemini: { | |
rulesName: 'GEMINI.md', | |
directory: '.gemini' | |
}, | |
claude: { | |
rulesName: 'CLAUDE.md', | |
directory: '.claude' | |
}, | |
copilot: { | |
rulesName: 'copilot-instructions.md', | |
directory: '.github', | |
}, | |
windsurf: { | |
rulesName: 'guidelines.md', | |
directory: path.join('.windsurf', 'rules'), | |
}, | |
// Cursor file has a front matter section. | |
cursor: { | |
rulesName: 'cursor.mdc', | |
directory: path.join('.cursor', 'rules'), | |
frontmatter: `---\ncontext: true\npriority: high\nscope: project\n---`, | |
}, | |
}; |
frontmatter?: string; | ||
} | ||
|
||
function addAiContextFile(options: ConfigOptions): Rule { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function addAiContextFile(options: ConfigOptions): Rule { | |
export default function(options: ConfigOptions): Rule { |
export default function (options: ConfigOptions): Rule { | ||
return addAiContextFile(options); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export default function (options: ConfigOptions): Rule { | |
return addAiContextFile(options); | |
} |
return async (host) => { | ||
const workspace = await readWorkspace(host); | ||
const project = workspace.projects.get(options.project); | ||
if (!project) { | ||
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workspace logic seems unused.
return async (host) => { | |
const workspace = await readWorkspace(host); | |
const project = workspace.projects.get(options.project); | |
if (!project) { | |
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`); | |
} | |
return (host) => { | |
const rules = files.map(({ rulesName, directory, frontmatter }) => | ||
mergeWith( | ||
apply(url('./files'), [ | ||
// Keep only the single source template | ||
filter((p) => p.endsWith('__rulesName__.template')), | ||
applyTemplates({ | ||
...strings, | ||
rulesName, | ||
frontmatter: frontmatter ?? '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT:
const rules = files.map(({ rulesName, directory, frontmatter }) => | |
mergeWith( | |
apply(url('./files'), [ | |
// Keep only the single source template | |
filter((p) => p.endsWith('__rulesName__.template')), | |
applyTemplates({ | |
...strings, | |
rulesName, | |
frontmatter: frontmatter ?? '', | |
const rules = files.map(({ rulesName, directory, frontmatter = '' }) => | |
mergeWith( | |
apply(url('./files'), [ | |
// Keep only the single source template | |
filter((p) => p.endsWith('__rulesName__.template')), | |
applyTemplates({ | |
...strings, | |
rulesName, | |
frontmatter, |
expect(tree.readContent('.gemini/GEMINI.md').length).toBeGreaterThan(0); | ||
expect(tree.readContent('.github/copilot-instructions.md').length).toBeGreaterThan(0); | ||
expect(tree.readContent('.cursor/rules/cursor.mdc').length).toBeGreaterThan(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(tree.readContent('.gemini/GEMINI.md').length).toBeGreaterThan(0); | |
expect(tree.readContent('.github/copilot-instructions.md').length).toBeGreaterThan(0); | |
expect(tree.readContent('.cursor/rules/cursor.mdc').length).toBeGreaterThan(0); | |
expect(tree.exists('.gemini/GEMINI.md')).toBeTrue(); | |
expect(tree.exists('.github/copilot-instructions.md')).toBeTrue(); | |
expect(tree.exists('.cursor/rules/cursor.mdc')).toBeTrue(); |
); | ||
}); | ||
|
||
it('should create a GEMINI.MD file', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most of the cases here, we can replace tree.readContent
with tree.exists
as we are not checking for text that is generated.
"additionalProperties": false, | ||
"description": "Generates configuration files for your project. These files control various aspects of your project's build process, testing, and browser compatibility. This schematic helps you create or update essential configuration files with ease.", | ||
"properties": { | ||
"project": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This option seems to be redundant, as the config is not generated for a project but for a workspace.
"title": "Angular Config File Options Schema", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"description": "Generates configuration files for your project. These files control various aspects of your project's build process, testing, and browser compatibility. This schematic helps you create or update essential configuration files with ease.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description seems wrong.
const configOptions: ConfigOptions | undefined = options.aiConfig?.length | ||
? { | ||
project: options.name, | ||
tool: options.aiConfig as unknown as AiTool[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
casting should not longer be needed.
tool: options.aiConfig as unknown as AiTool[], | |
tool: options.aiConfig, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above, and commit message has the wrong scope as it should be "@schematics/angular" and description.
ng generate ai-config
to prompt support tools.ng generate ai-config --tool=gemini
to specify the tool. (multiple args accepted)ng new --ai-config gemini
to create a new project with AI configuration. (multiple args accepted)Supported ai tools: gemini, claude, copilot, windsurf, cursor.