From 5cc5c7653a39b28bdd30762dce575620d9b1a2fd Mon Sep 17 00:00:00 2001 From: stefan judis Date: Thu, 25 Jun 2026 16:31:04 +0200 Subject: [PATCH 1/3] feat(cli): deprecate rules command in favor of skills The rules command read the bundled checkly.rules.md and wrote it into AI IDE config folders. That capability now lives in `checkly skills`, so rules is reduced to a deprecation notice pointing users there. --- .../cli/src/commands/__tests__/rules.spec.ts | 31 ++++ packages/cli/src/commands/rules.ts | 142 +----------------- 2 files changed, 36 insertions(+), 137 deletions(-) create mode 100644 packages/cli/src/commands/__tests__/rules.spec.ts diff --git a/packages/cli/src/commands/__tests__/rules.spec.ts b/packages/cli/src/commands/__tests__/rules.spec.ts new file mode 100644 index 000000000..729e0cd63 --- /dev/null +++ b/packages/cli/src/commands/__tests__/rules.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it, vi } from 'vitest' + +import Rules from '../rules.js' + +const mockConfig = { + runHook: vi.fn().mockResolvedValue({ successes: [], failures: [] }), +} as any + +function createCommand (...args: string[]) { + const cmd = new Rules(args, mockConfig) + cmd.log = vi.fn() as any + return cmd +} + +function getLogged (cmd: Rules): string[] { + return (cmd.log as ReturnType).mock.calls.map( + (call: string[]) => call[0], + ) +} + +describe('rules', () => { + it('prints the deprecation message pointing to skills', async () => { + const cmd = createCommand() + + await cmd.run() + + const logged = getLogged(cmd) + expect(logged.some(m => m.includes('Rules were deprecated.'))).toBe(true) + expect(logged.some(m => m.includes('npx checkly skills'))).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/rules.ts b/packages/cli/src/commands/rules.ts index 3ed224ac0..b8253d4f1 100644 --- a/packages/cli/src/commands/rules.ts +++ b/packages/cli/src/commands/rules.ts @@ -1,147 +1,15 @@ import { BaseCommand } from './baseCommand.js' -import { readFile, writeFile, access, mkdir } from 'fs/promises' -import path, { join } from 'path' -import { constants } from 'fs' -import prompts from 'prompts' -import { fileURLToPath } from 'node:url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -const BASE_RULES_FILE_PATH = join(__dirname, '../ai-context/checkly.rules.md') - -// AI IDE configurations mapping -const AI_IDE_CONFIGS = { - 'Windsurf': { - rulesFolder: '.windsurf/rules', - rulesFileName: 'checkly.md', - }, - 'GitHub Copilot': { - rulesFolder: '.github/instructions', - rulesFileName: 'checkly.instructions.md', - }, - 'Cursor': { - rulesFolder: '.cursor/rules', - rulesFileName: 'checkly.mdc', - }, - 'Plain Markdown (checkly.md)': { - rulesFolder: '.', - rulesFileName: 'checkly.md', - }, -} as const export default class Rules extends BaseCommand { static hidden = false static readOnly = true static idempotent = true + static state = 'deprecated' static description = - 'Generate a rules file to use with AI IDEs and Copilots.' - - async run (): Promise { - // Read the base rules file - const rulesContent = await this.readBaseRulesFile() - if (!rulesContent) { - this.error(`Failed to read rules file at ${BASE_RULES_FILE_PATH}`) - } - - // In non-interactive mode, print rules to stdout and exit - const isNonInteractive = !process.stdin.isTTY - || !process.stdout.isTTY - || process.env.CI - || process.env.CHECKLY_NON_INTERACTIVE - if (isNonInteractive) { - this.log(rulesContent) - return - } - - try { - // Create options for multiselect - offer all configs from AI_IDE_CONFIGS - const choices = Object.entries(AI_IDE_CONFIGS).map(([ideName, ideConfig]) => { - return { - title: `${ideName} (${path.join(ideConfig.rulesFolder, ideConfig.rulesFileName)})`, - value: ideConfig, - selected: false, - } - }) - - const isNonInteractive = !process.stdin.isTTY - || !process.stdout.isTTY - || process.env.CI - || process.env.CHECKLY_NON_INTERACTIVE - - // Interactive mode - show multiselect - const { configs: selectedConfig } = await prompts({ - type: 'select', - name: 'configs', - message: 'Select the AI IDE configurations to generate rules for:', - choices, - initial: 0, - }) - - if (!selectedConfig) { - this.log('Operation cancelled.') - return - } - - this.log(`Generating rules`) - - // Create rules directory if it doesn't exist - const rulesDir = join(process.cwd(), selectedConfig.rulesFolder) - try { - await mkdir(rulesDir, { recursive: true }) - } catch { - // Directory might already exist, ignore error - } - - // Determine the target file path - const rulesFilePath = join(rulesDir, selectedConfig.rulesFileName) - - // Check if file already exists and ask for confirmation (only in interactive mode) - let shouldOverwrite = true - if (!isNonInteractive) { - shouldOverwrite = await this.confirmOverwrite(rulesFilePath) - } - - if (!shouldOverwrite) { - this.log(`Skipped ${rulesFilePath}`) - return - } - - // Save the rules file - await writeFile(rulesFilePath, rulesContent, 'utf8') - - this.log(`✅ Successfully saved Checkly rules file to: ${rulesFilePath}`) - } catch (error) { - this.error(`Failed to generate rules file: ${error}`) - } - } - - private async readBaseRulesFile (): Promise { - try { - return await readFile(BASE_RULES_FILE_PATH, 'utf8') - } catch (error) { - throw new Error( - `Failed to read base rules file at ${BASE_RULES_FILE_PATH}: ${error}`, - { cause: error }, - ) - } - } - - private async confirmOverwrite (targetPath: string): Promise { - try { - await access(targetPath, constants.F_OK) - - // File exists, ask for confirmation - const { overwrite } = await prompts({ - type: 'confirm', - name: 'overwrite', - message: `Rules file already exists at ${targetPath}. Do you want to overwrite it?`, - initial: false, - }) + 'Deprecated. Use `checkly skills` instead.' - return overwrite ?? false - } catch { - // File doesn't exist, no need to confirm - return true - } + run (): Promise { + this.log('Rules were deprecated. Use `npx checkly skills`.') + return Promise.resolve() } } From 2f85b68389b8d2b781d28f7f174b6a7f40176785 Mon Sep 17 00:00:00 2001 From: stefan judis Date: Thu, 25 Jun 2026 16:36:34 +0200 Subject: [PATCH 2/3] chore(cli): stop generating checkly.rules.md ai-context artifact The rules command no longer reads checkly.rules.md, so drop its generation from the AI-context pipeline along with the now-dead heading helpers, and remove the step that attached it to GitHub Releases. The finalize-release job keeps marking the release as latest. --- .github/workflows/release.yml | 11 +----- CONTRIBUTING.md | 4 +-- packages/cli/scripts/prepare-ai-context.ts | 41 ++-------------------- 3 files changed, 6 insertions(+), 50 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a380732ae..49b7437fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,22 +171,13 @@ jobs: SLACK_TITLE: ':white_check_mark: NPM publish succeeded' SLACK_MESSAGE: by ${{ github.actor }} SLACK_FOOTER: '' - # Attach checkly.rules.md to the GitHub Release and mark it as latest + # Mark the GitHub Release as latest if it's the highest version finalize-release: runs-on: ubuntu-latest needs: release permissions: contents: write steps: - - name: Download LLM rules artifact - uses: actions/download-artifact@v4 - with: - name: llm-rules-release - path: llm-rules-release - - name: Upload checkly.rules.md to GitHub Release - env: - GH_TOKEN: ${{ github.token }} - run: gh release upload ${{ github.event.release.tag_name }} llm-rules-release/checkly.rules.md --repo ${{ github.repository }} - name: Mark release as latest if version is highest env: GH_TOKEN: ${{ github.token }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afb9e2517..7a9b3030c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,12 +105,12 @@ Both packages [checkly](https://www.npmjs.com/package/checkly) and [create-cli]( To release packages to NPM: -1. Publish a Github Release with a valid tag `#.#.#` (do **not** include a `v` prefix) and click the `Generate release notes` button to auto-generate notes following format defined [here](https://github.com/checkly/checkly-cli/blob/main/.github/release.yml). **Uncheck "Set as the latest release"** — the workflow will mark it as latest automatically after attaching the `checkly.rules.md` asset. +1. Publish a Github Release with a valid tag `#.#.#` (do **not** include a `v` prefix) and click the `Generate release notes` button to auto-generate notes following format defined [here](https://github.com/checkly/checkly-cli/blob/main/.github/release.yml). **Uncheck "Set as the latest release"** — the workflow will mark it as latest automatically. 2. When release is published the Github action is triggered. It builds and publishes `#.#.#-prerelease` prereleases (for both packages). 3. Test the prerelease version to make sure that it's working. * To test `npm create checkly`, run `CHECKLY_CLI_VERSION=4.6.2 npm create checkly@4.6.2-prerelease-c6e8165` (substituting `4.6.2` and `4.6.2-prerelease` for your versions, which you can find at https://www.npmjs.com/package/checkly?activeTab=versions). `CHECKLY_CLI_VERSION` is needed since the `create-checkly` package looks up the corresponding tag on GitHub to pull project templates. * Ensure your project `package.json` has `"checkly": "4.6.2-prerelease-c6e8165"` -4. A `production` deployment will be waiting for approval. After it's approved, the `#.#.#` version will be published. The workflow will then automatically attach `checkly.rules.md` to the GitHub Release and mark it as latest (if the version is higher than the current latest). +4. A `production` deployment will be waiting for approval. After it's approved, the `#.#.#` version will be published. The workflow will then automatically mark the GitHub Release as latest (if the version is higher than the current latest). ### Catching issues in prerelease diff --git a/packages/cli/scripts/prepare-ai-context.ts b/packages/cli/scripts/prepare-ai-context.ts index 0fc23f957..f60729ddb 100644 --- a/packages/cli/scripts/prepare-ai-context.ts +++ b/packages/cli/scripts/prepare-ai-context.ts @@ -4,7 +4,7 @@ import { ACTIONS, EXAMPLE_CONFIGS } from '../src/ai-context/context' const EXAMPLES_DIR = join(__dirname, '../gen/') const AI_CONTEXT_DIR = join(__dirname, '../src/ai-context') -const RULES_OUTPUT_DIR = join(__dirname, '../dist/ai-context') +const DIST_AI_CONTEXT_DIR = join(__dirname, '../dist/ai-context') // Reference files served by the CLI's `checkly skills [action] [reference]` command const COMMAND_REFERENCES_DIR = join(__dirname, '../dist/ai-context/skills-command/references') @@ -13,21 +13,6 @@ const COMMAND_REFERENCES_DIR = join(__dirname, '../dist/ai-context/skills-comman const PUBLIC_SKILLS_DIR = join(__dirname, '../dist/ai-context/public-skills') const PUBLIC_SKILL_DIR = join(PUBLIC_SKILLS_DIR, 'checkly') -function stripYamlFrontmatter (content: string): string { - const frontmatterRegex = /^---\r?\n[\s\S]*?\r?\n---\r?\n+/ - return content.replace(frontmatterRegex, '') -} - -// Demote headings by two levels (# -> ###, ## -> ####) to maintain proper -// heading hierarchy in checkly.rules.md. -function demoteHeadings (content: string): string { - return content.replace(/^(#+)/gm, '##$1') -} - -function normalizeBlankLines (content: string): string { - return content.replace(/\n{3,}/g, '\n\n') -} - async function writeOutput (content: string, dir: string, filename: string): Promise { await mkdir(dir, { recursive: true }) const outputPath = join(dir, filename) @@ -102,8 +87,6 @@ async function prepareContext () { const examples = await readExampleCode() // Process all actions — reference files, action headers, and standalone actions - const configureReferenceContents: string[] = [] - for (const action of ACTIONS) { if ('references' in action) { for (const ref of action.references) { @@ -113,10 +96,6 @@ async function prepareContext () { ) refContent = replaceExamples(refContent, examples) await writeOutput(refContent, COMMAND_REFERENCES_DIR, `${ref.id}.md`) - - if (action.id === 'configure') { - configureReferenceContents.push(refContent) - } } let actionContent = await readFile( @@ -144,29 +123,15 @@ async function prepareContext () { .replace('', generateSkillCommands()) await writeOutput(skillTemplate, PUBLIC_SKILL_DIR, 'SKILL.md') - // Generate checkly.rules.md (configure header + all configure-* references concatenated) - const configureContent = await readFile( - join(COMMAND_REFERENCES_DIR, 'configure.md'), - 'utf8', - ) - const demotedReferences = configureReferenceContents - .map(demoteHeadings).join('\n\n') - const rulesContent = normalizeBlankLines(stripYamlFrontmatter( - configureContent - + '\n' - + demotedReferences, - )) - await writeOutput(rulesContent, RULES_OUTPUT_DIR, 'checkly.rules.md') - // Copy README const readme = await readFile(join(AI_CONTEXT_DIR, 'README.md'), 'utf8') await writeOutput(readme, PUBLIC_SKILL_DIR, 'README.md') // Copy onboarding assets to dist for (const dir of ['onboarding-boilerplate', 'onboarding-prompts']) { - await cp(join(AI_CONTEXT_DIR, dir), join(RULES_OUTPUT_DIR, dir), { recursive: true }) + await cp(join(AI_CONTEXT_DIR, dir), join(DIST_AI_CONTEXT_DIR, dir), { recursive: true }) // eslint-disable-next-line no-console - console.log(`Copied ${dir} to ${join(RULES_OUTPUT_DIR, dir)}`) + console.log(`Copied ${dir} to ${join(DIST_AI_CONTEXT_DIR, dir)}`) } } catch (error) { // eslint-disable-next-line no-console From 9869cf06b4311ab0a9b6ae3788f6b1cca012a0c3 Mon Sep 17 00:00:00 2001 From: stefan judis Date: Thu, 25 Jun 2026 19:48:24 +0200 Subject: [PATCH 3/3] test(e2e): update help output for deprecated rules command --- packages/cli/e2e/__tests__/help.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/e2e/__tests__/help.spec.ts b/packages/cli/e2e/__tests__/help.spec.ts index db8a79c53..14a91d72b 100644 --- a/packages/cli/e2e/__tests__/help.spec.ts +++ b/packages/cli/e2e/__tests__/help.spec.ts @@ -68,7 +68,7 @@ describe('help', () => { logout Log out and clear any local credentials. members List account members and pending invites. rca Trigger and retrieve root cause analyses. - rules Generate a rules file to use with AI IDEs and Copilots. + rules Deprecated. Use \`checkly skills\` instead. runtimes List all supported runtimes and dependencies. skills Show Checkly AI skills, actions and their references. status-pages List and manage status pages in your Checkly account.