From a17e8c666362606a163133ab693e75b91a4b77fc Mon Sep 17 00:00:00 2001 From: Nicolas Merget Date: Mon, 20 Oct 2025 15:16:43 +0200 Subject: [PATCH] chore: improve agent-cli by generating amazonq --- .changeset/sixty-cases-bake.md | 5 + .gitignore | 1 + packages/agent-cli/package.json | 3 +- packages/agent-cli/src/amazonq/index.ts | 48 ++++++++++ packages/agent-cli/src/cli.ts | 6 +- packages/agent-cli/src/copilot/index.ts | 96 +++---------------- packages/agent-cli/src/index.ts | 23 +++++ packages/agent-cli/src/utils/index.ts | 86 +++++++++++++++++ .../test/__snapshots__/index.spec.ts.snap | 25 ++++- packages/agent-cli/test/index.spec.ts | 7 +- .../agent/Best-Practise_Common-AI-Mistakes.md | 15 +++ packages/components/agent/_instructions.md | 15 +-- 12 files changed, 219 insertions(+), 111 deletions(-) create mode 100644 .changeset/sixty-cases-bake.md create mode 100644 packages/agent-cli/src/amazonq/index.ts create mode 100644 packages/agent-cli/src/index.ts create mode 100644 packages/agent-cli/src/utils/index.ts create mode 100644 packages/components/agent/Best-Practise_Common-AI-Mistakes.md diff --git a/.changeset/sixty-cases-bake.md b/.changeset/sixty-cases-bake.md new file mode 100644 index 000000000000..94dc2caf8a4d --- /dev/null +++ b/.changeset/sixty-cases-bake.md @@ -0,0 +1,5 @@ +--- +"@db-ux/agent-cli": patch +--- + +chore: generate AmazonQ rule file with @db-ux/agent-cli diff --git a/.gitignore b/.gitignore index cf9379667f66..e7d0292811a6 100644 --- a/.gitignore +++ b/.gitignore @@ -73,5 +73,6 @@ showcases/patternhub/public/iframe-resizer/* /output/**/docs/ /output/**/agent/ /packages/agent-cli/test/.github/copilot-instructions.md +/packages/agent-cli/test/.amazonq/rules/db-ux.md /core-web.iml /build-storybooks/ diff --git a/packages/agent-cli/package.json b/packages/agent-cli/package.json index 7e9d78f266a0..dca301b93d01 100644 --- a/packages/agent-cli/package.json +++ b/packages/agent-cli/package.json @@ -24,7 +24,8 @@ "copy-build:package.json": "cpr package.json ../../build-outputs/agent-cli/package.json --overwrite", "copy-build:readme": "cpr README.md ../../build-outputs/agent-cli/README.md --overwrite", "test": "vitest run --config vitest.config.ts", - "test:cli": "tsx src/cli.ts --help" + "test:cli": "tsx src/cli.ts --help", + "test:update": "vitest run --config vitest.config.ts --update" }, "dependencies": { "commander": "14.0.1" diff --git a/packages/agent-cli/src/amazonq/index.ts b/packages/agent-cli/src/amazonq/index.ts new file mode 100644 index 000000000000..8ffbc7abff6b --- /dev/null +++ b/packages/agent-cli/src/amazonq/index.ts @@ -0,0 +1,48 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { getInstructions } from '../utils'; + +export const generateAmazonQ = (rootPath: string) => { + const outputFolder = path.resolve(rootPath, '.amazonq', 'rules'); + + if (!fs.existsSync(outputFolder)) { + fs.mkdirSync(outputFolder, { recursive: true }); + } + + const amazonqInstructionsPath = path.join(outputFolder, 'db-ux.md'); + if (!fs.existsSync(amazonqInstructionsPath)) { + fs.writeFileSync(amazonqInstructionsPath, ''); + } + + const amazonqInstructionsContent = getInstructions(rootPath); + + if (amazonqInstructionsContent) { + let amazonqFileContent = fs.readFileSync( + amazonqInstructionsPath, + 'utf8' + ); + const startMarker = + '--- START: DB UX Amazon Q Instructions – do not edit below ---'; + const endMarker = + '--- END: DB UX Amazon Q Instructions – do not edit above ---'; + const startIndex = amazonqFileContent.indexOf(startMarker); + const endIndex = amazonqFileContent.indexOf(endMarker); + if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) { + amazonqFileContent = ( + amazonqFileContent.slice(0, startIndex) + + amazonqFileContent.slice(endIndex + endMarker.length) + ).trim(); + } + + amazonqFileContent += ` +${startMarker} + +> **NOTE**: Always use "DB UX" components and variables as project's conventions +${amazonqInstructionsContent} + +${endMarker} +`; + + fs.writeFileSync(amazonqInstructionsPath, amazonqFileContent); + } +}; diff --git a/packages/agent-cli/src/cli.ts b/packages/agent-cli/src/cli.ts index f06510772929..fe0f5d1168fe 100644 --- a/packages/agent-cli/src/cli.ts +++ b/packages/agent-cli/src/cli.ts @@ -1,11 +1,7 @@ #!/usr/bin/env node import { program } from 'commander'; -import { generateCopilot } from './copilot'; - -const action = async (rootPath: string = '.') => { - generateCopilot(rootPath); -}; +import { action } from './index'; const startProgram = ( name: string, diff --git a/packages/agent-cli/src/copilot/index.ts b/packages/agent-cli/src/copilot/index.ts index d8b6026dbd71..6f6ee642fb93 100644 --- a/packages/agent-cli/src/copilot/index.ts +++ b/packages/agent-cli/src/copilot/index.ts @@ -1,90 +1,12 @@ import fs from 'node:fs'; import path from 'node:path'; - -function findAllNodeModulesDirectories( - directory: string, - found: string[] = [] -): string[] { - if (!fs.existsSync(directory)) { - return found; - } - - const entries = fs - .readdirSync(directory, { withFileTypes: true }) - .sort((a, b) => a.name.localeCompare(b.name, 'en')); - for (const entry of entries) { - if (entry.isDirectory()) { - if (entry.name === 'node_modules') { - found.push(path.join(directory, entry.name)); - } else if (!entry.name.startsWith('.')) { - findAllNodeModulesDirectories( - path.join(directory, entry.name), - found - ); - } - } - } - - return found; -} +import { getInstructions } from '../utils'; export const generateCopilot = (rootPath: string) => { const outputFolder = path.resolve(rootPath, '.github'); - const nodeModulesDirectories = findAllNodeModulesDirectories(rootPath); - if (nodeModulesDirectories.length === 0) { - console.error('No node_modules folders found.'); - return; - } - - let copilotInstructionsContent = ''; - - for (const nodeModulesPath of nodeModulesDirectories) { - const databaseUxPaths = [ - path.join(nodeModulesPath, '@db-ux/'), - path.join(nodeModulesPath, '@db-ux-inner-source/') - ]; - - for (const databaseUxPath of databaseUxPaths) { - if (!fs.existsSync(databaseUxPath)) { - continue; - } - - const packages = fs.readdirSync(databaseUxPath, { - withFileTypes: true - }); - for (const package_ of packages) { - if (package_.isDirectory()) { - const instructionsPath = path.join( - databaseUxPath, - package_.name, - 'agent', - '_instructions.md' - ); - if (fs.existsSync(instructionsPath)) { - let content = fs.readFileSync(instructionsPath, 'utf8'); - const relativePath = path.relative( - rootPath, - path.join(databaseUxPath, package_.name) - ); - content = content - .replaceAll( - '__agent-path__', - relativePath.replaceAll('\\', '/') - ) - .replaceAll( - '**agent-path**', - relativePath.replaceAll('\\', '/') - ); - copilotInstructionsContent += `\n# ${path.basename(databaseUxPath)}/${package_.name}\n${content}\n`; - } - } - } - } - } - if (!fs.existsSync(outputFolder)) { - fs.mkdirSync(outputFolder); + fs.mkdirSync(outputFolder, { recursive: true }); } const copilotInstructionsPath = path.join( @@ -95,7 +17,9 @@ export const generateCopilot = (rootPath: string) => { fs.writeFileSync(copilotInstructionsPath, ''); } - if (copilotInstructionsContent.trim()) { + const copilotInstructionsContent = getInstructions(rootPath); + + if (copilotInstructionsContent) { let copilotFileContent = fs.readFileSync( copilotInstructionsPath, 'utf8' @@ -114,10 +38,12 @@ export const generateCopilot = (rootPath: string) => { } copilotFileContent += ` - ${startMarker} - > **NOTE**: Always use "DB UX" components and variables as project's conventions - ${copilotInstructionsContent} - ${endMarker} +${startMarker} + +> **NOTE**: Always use "DB UX" components and variables as project's conventions +${copilotInstructionsContent} + +${endMarker} `; fs.writeFileSync(copilotInstructionsPath, copilotFileContent); diff --git a/packages/agent-cli/src/index.ts b/packages/agent-cli/src/index.ts new file mode 100644 index 000000000000..00d5a9cb291c --- /dev/null +++ b/packages/agent-cli/src/index.ts @@ -0,0 +1,23 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { generateAmazonQ } from './amazonq'; +import { generateCopilot } from './copilot'; + +export const action = async (rootPath: string = '.') => { + const hasCopilot = fs.existsSync( + path.join(rootPath, '.github', 'copilot-instructions.md') + ); + const hasAmazonQ = fs.existsSync(path.join(rootPath, '.amazonq', 'rules')); + + if (!hasCopilot && !hasAmazonQ) { + generateCopilot(rootPath); + generateAmazonQ(rootPath); + } else if (hasCopilot && hasAmazonQ) { + generateCopilot(rootPath); + generateAmazonQ(rootPath); + } else if (hasCopilot) { + generateCopilot(rootPath); + } else { + generateAmazonQ(rootPath); + } +}; diff --git a/packages/agent-cli/src/utils/index.ts b/packages/agent-cli/src/utils/index.ts new file mode 100644 index 000000000000..bb524948f510 --- /dev/null +++ b/packages/agent-cli/src/utils/index.ts @@ -0,0 +1,86 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +function findAllNodeModulesDirectories( + directory: string, + found: string[] = [] +): string[] { + if (!fs.existsSync(directory)) { + return found; + } + + const entries = fs + .readdirSync(directory, { withFileTypes: true }) + .sort((a, b) => a.name.localeCompare(b.name, 'en')); + for (const entry of entries) { + if (entry.isDirectory()) { + if (entry.name === 'node_modules') { + found.push(path.join(directory, entry.name)); + } else if (!entry.name.startsWith('.')) { + findAllNodeModulesDirectories( + path.join(directory, entry.name), + found + ); + } + } + } + + return found; +} + +export const getInstructions = (rootPath: string): string => { + let copilotInstructionsContent = ''; + const nodeModulesDirectories = findAllNodeModulesDirectories(rootPath); + if (nodeModulesDirectories.length === 0) { + console.error('No node_modules folders found.'); + return ''; + } + + for (const nodeModulesPath of nodeModulesDirectories) { + const databaseUxPaths = [ + path.join(nodeModulesPath, '@db-ux/'), + path.join(nodeModulesPath, '@db-ux-inner-source/') + ]; + + for (const databaseUxPath of databaseUxPaths) { + if (!fs.existsSync(databaseUxPath)) { + continue; + } + + const packages = fs.readdirSync(databaseUxPath, { + withFileTypes: true + }); + for (const package_ of packages) { + if (package_.isDirectory()) { + const instructionsPath = path.join( + databaseUxPath, + package_.name, + 'agent', + '_instructions.md' + ); + if (fs.existsSync(instructionsPath)) { + let content = fs.readFileSync(instructionsPath, 'utf8'); + const relativePath = path.relative( + rootPath, + path.join(databaseUxPath, package_.name) + ); + content = content + .replaceAll( + '__agent-path__', + relativePath.replaceAll('\\', '/') + ) + .replaceAll( + '**agent-path**', + relativePath.replaceAll('\\', '/') + ); + copilotInstructionsContent += `\n# ${path.basename(databaseUxPath)}/${package_.name}\n${content}\n`; + } + } + } + } + } + + copilotInstructionsContent = copilotInstructionsContent.trim(); + + return copilotInstructionsContent; +}; diff --git a/packages/agent-cli/test/__snapshots__/index.spec.ts.snap b/packages/agent-cli/test/__snapshots__/index.spec.ts.snap index 45ca6551d782..8068302e737a 100644 --- a/packages/agent-cli/test/__snapshots__/index.spec.ts.snap +++ b/packages/agent-cli/test/__snapshots__/index.spec.ts.snap @@ -2,9 +2,9 @@ exports[`default > check if docs are created 1`] = ` " - --- START: DB UX Copilot Instructions – do not edit below --- - > **NOTE**: Always use "DB UX" components and variables as project's conventions - +--- START: DB UX Copilot Instructions – do not edit below --- + +> **NOTE**: Always use "DB UX" components and variables as project's conventions # @db-ux/components - use y for frontend/node_modules/@db-ux/components/test.md @@ -12,7 +12,22 @@ exports[`default > check if docs are created 1`] = ` # @db-ux/foundations - use x for frontend/node_modules/@db-ux/foundations/test.md - - --- END: DB UX Copilot Instructions – do not edit above --- +--- END: DB UX Copilot Instructions – do not edit above --- " `; + +exports[`default > check if docs are created 2`] = ` +" +--- START: DB UX Amazon Q Instructions – do not edit below --- + +> **NOTE**: Always use "DB UX" components and variables as project's conventions +# @db-ux/components +- use y for frontend/node_modules/@db-ux/components/test.md + + +# @db-ux/foundations +- use x for frontend/node_modules/@db-ux/foundations/test.md + +--- END: DB UX Amazon Q Instructions – do not edit above --- +" +`; diff --git a/packages/agent-cli/test/index.spec.ts b/packages/agent-cli/test/index.spec.ts index 55bf527f84d1..235afd36f043 100644 --- a/packages/agent-cli/test/index.spec.ts +++ b/packages/agent-cli/test/index.spec.ts @@ -1,15 +1,18 @@ import fs from 'node:fs'; import path from 'node:path'; import { describe, expect, test } from 'vitest'; -import { generateCopilot } from '../src/copilot'; +import { action } from '../src'; describe('default', () => { test('check if docs are created', async () => { const copilotFile = path.resolve( 'test/.github/copilot-instructions.md' ); - generateCopilot('test'); + const amazonqFile = path.resolve('test/.amazonq/rules/db-ux.md'); + + await action('test'); expect(fs.readFileSync(copilotFile).toString()).toMatchSnapshot(); + expect(fs.readFileSync(amazonqFile).toString()).toMatchSnapshot(); }); }); diff --git a/packages/components/agent/Best-Practise_Common-AI-Mistakes.md b/packages/components/agent/Best-Practise_Common-AI-Mistakes.md new file mode 100644 index 000000000000..2cd05a62111f --- /dev/null +++ b/packages/components/agent/Best-Practise_Common-AI-Mistakes.md @@ -0,0 +1,15 @@ +# Best Practise / Common AI mistakes + +## `DBButton` or `db-button` + +- available variants are `outlined`, `brand`, `filled`, `ghost` +- always use variant `outlined` as default +- use variant `brand` as CTA or primary action +- if `noText`/`no-text` property is used add a `DBTooltip` or `db-tooltip` inside the Button +- always add a `type` as property as best practise +- there is no `size=large` button + +## `DBStack` or `db-stack` + +- there is no property `gap="fix-md"`, available values are `small`, `medium`, etc. +- there is no property `direction="horizontal"`, available values are `row`, `column`, etc. diff --git a/packages/components/agent/_instructions.md b/packages/components/agent/_instructions.md index 721a5482ebea..902f8b3891e7 100644 --- a/packages/components/agent/_instructions.md +++ b/packages/components/agent/_instructions.md @@ -1,14 +1,3 @@ -## Common AI mistakes +## Best Practise / Common AI mistakes -### `DBButton` or `db-button` - -- available variants are `outlined`, `brand`, `filled`, `ghost` -- always use variant `outlined` as default -- use variant `brand` as CTA or primary action -- if `noText`/`no-text` property is used add a `DBTooltip` or `db-tooltip` inside the Button -- always add a `type` as property as best practise - -### `DBStack` or `db-stack` - -- there is no property `gap="fix-md"`, available values are `small`, `medium`, etc. -- there is no property `direction="horizontal"`, available values are `row`, `column`, etc. +- always read this file if you use Components: `**agent-path**/agent/Best-Practise_Common-AI-Mistakes.md`