agent-rules
Version:
Rules and instructions for agentic coding tools like Cursor, Claude CLI, Gemini CLI, Qodo, Cline and more
186 lines (182 loc) • 7.77 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/bin/cli.ts
import { intro, outro, select, multiselect } from "@clack/prompts";
import { styleText, debuglog as debuglog2 } from "util";
// src/main.ts
import path from "path";
import fs from "fs/promises";
import { fileURLToPath } from "url";
import { debuglog } from "util";
var debug = debuglog("agent-rules");
var templateRoot = "__template__";
var mapAiAppsToDirectories = {
"github-copilot": {
directory: ".github/instructions",
filesSuffix: ".instructions.md"
}
};
function resolvePackageRootDirectoryForTemplates() {
let guessedDirName = "";
try {
if (typeof import.meta !== "undefined" && import.meta.url) {
const __filename = fileURLToPath(import.meta.url);
guessedDirName = path.dirname(__filename);
} else {
guessedDirName = __dirname;
}
} catch (error) {
guessedDirName = __dirname;
}
if (guessedDirName.endsWith("src")) {
return path.resolve(guessedDirName, "..");
} else if (guessedDirName.endsWith("dist/bin") || guessedDirName.endsWith("dist\\bin")) {
return path.resolve(guessedDirName, "..");
} else {
return guessedDirName;
}
}
__name(resolvePackageRootDirectoryForTemplates, "resolvePackageRootDirectoryForTemplates");
function getAiAppDirectory(aiApp) {
const app = Object.hasOwn(mapAiAppsToDirectories, aiApp) ? mapAiAppsToDirectories[aiApp] : null;
if (!app) {
throw new Error(`AI App "${aiApp}" is not supported.`);
}
return app;
}
__name(getAiAppDirectory, "getAiAppDirectory");
async function resolveTemplateDirectory(scaffoldInstructions) {
const { codeLanguage, codeTopic } = scaffoldInstructions;
const currentFileDirectory = resolvePackageRootDirectoryForTemplates();
const templateDirectory = path.join(currentFileDirectory, templateRoot, codeLanguage, codeTopic);
const resolvedTemplateDirectory = path.resolve(templateDirectory);
try {
const templateStats = await fs.stat(resolvedTemplateDirectory);
if (!templateStats.isDirectory()) {
throw new Error(`Template directory is not a directory: ${resolvedTemplateDirectory}`);
}
} catch (error) {
throw new Error(`Template directory not found: ${resolvedTemplateDirectory}`);
}
return resolvedTemplateDirectory;
}
__name(resolveTemplateDirectory, "resolveTemplateDirectory");
async function createTargetDirectory(directory) {
const resolvedTargetDirectory = path.resolve(directory);
await fs.mkdir(resolvedTargetDirectory, { recursive: true });
return resolvedTargetDirectory;
}
__name(createTargetDirectory, "createTargetDirectory");
function generateTargetFileName(templateFileName, filesSuffix) {
const parsedFile = path.parse(templateFileName);
let baseName = parsedFile.name;
if (baseName.endsWith(".instructions")) {
baseName = baseName.replace(/\.instructions$/, "");
}
return `${baseName}${filesSuffix}`;
}
__name(generateTargetFileName, "generateTargetFileName");
function validateTargetPath(targetFilePath, resolvedTargetDirectory) {
const resolvedTargetFilePath = path.resolve(targetFilePath);
if (!resolvedTargetFilePath.startsWith(resolvedTargetDirectory)) {
throw new Error(`Invalid target path: ${targetFilePath}`);
}
return resolvedTargetFilePath;
}
__name(validateTargetPath, "validateTargetPath");
async function copyTemplateFile(templateFilePath, targetFilePath, resolvedTargetDirectory, filesSuffix) {
const sanitizedTemplateFile = path.basename(templateFilePath);
const fullTemplatePath = path.join(path.dirname(templateFilePath), sanitizedTemplateFile);
debug("Processing template file:", sanitizedTemplateFile);
try {
const stat = await fs.stat(fullTemplatePath);
if (stat.isFile()) {
const targetFileName = generateTargetFileName(sanitizedTemplateFile, filesSuffix);
const targetPath = path.join(targetFilePath, targetFileName);
const resolvedTargetFilePath = validateTargetPath(targetPath, resolvedTargetDirectory);
debug("Writing template file to target path:", resolvedTargetFilePath);
const templateContent = await fs.readFile(fullTemplatePath, "utf-8");
await fs.writeFile(resolvedTargetFilePath, templateContent, "utf-8");
}
} catch (error) {
console.warn(`Skipping file ${sanitizedTemplateFile}: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
__name(copyTemplateFile, "copyTemplateFile");
async function copyTemplateFiles(resolvedTemplateDirectory, resolvedTargetDirectory, filesSuffix) {
const templateFiles = await fs.readdir(resolvedTemplateDirectory);
for (const templateFile of templateFiles) {
const templateFilePath = path.join(resolvedTemplateDirectory, templateFile);
await copyTemplateFile(templateFilePath, resolvedTargetDirectory, resolvedTargetDirectory, filesSuffix);
}
}
__name(copyTemplateFiles, "copyTemplateFiles");
async function scaffoldAiAppInstructions(scaffoldInstructions) {
const { aiApp, codeLanguage, codeTopic } = scaffoldInstructions;
if (!aiApp || !codeLanguage || !codeTopic) {
throw new Error("Scaffold instructions must include aiApp and all other template choices.");
}
const aiAppConfig = getAiAppDirectory(aiApp);
const { directory, filesSuffix } = aiAppConfig;
debug(`Scaffolding AI App instructions in directory: ${directory} with files suffix: ${filesSuffix}`);
const resolvedTemplateDirectory = await resolveTemplateDirectory(scaffoldInstructions);
const resolvedTargetDirectory = await createTargetDirectory(directory);
await copyTemplateFiles(resolvedTemplateDirectory, resolvedTargetDirectory, filesSuffix);
}
__name(scaffoldAiAppInstructions, "scaffoldAiAppInstructions");
// src/bin/cli.ts
var debug2 = debuglog2("agent-rules");
async function init() {
intro(styleText(["bgMagentaBright", "black"], " Agent, rules!"));
const codeLanguage = "nodejs";
debug2("Selected code language:", codeLanguage);
const aiApp = await select({
message: "Which AI App would you like to generate agentic rules for?",
options: [
{ value: "claude-code", label: "Claude Code" },
{ value: "github-copilot", label: "GitHub Copilot" },
{ value: "cursor", label: "Cursor" }
],
initialValue: "github-copilot"
});
if (typeof aiApp === "symbol") {
throw new Error("Operation cancelled by user");
}
debug2("Selected AI App:", aiApp);
const topicChoices = await multiselect({
message: "Which topic do you want to generate agentic rules for?",
options: [
{ value: "secure-code", label: "Secure Coding", hint: "Apply security best practices for defensive coding in Node.js" },
{ value: "security-vulnerabilities", label: "Security Vulnerabilities", hint: "Scan and fix security vulnerabilities in Node.js application code and 3rd-party dependencies" },
{ value: "testing", label: "Testing", hint: "Establish mature testing strategy and test code guidelines in Node.js applications" }
],
required: true
});
if (typeof topicChoices === "symbol") {
throw new Error("Operation cancelled by user");
}
debug2("Selected code topic: ", topicChoices.join(", "));
for (const codeTopic of topicChoices) {
const templateChoices = {
aiApp,
codeLanguage,
codeTopic
};
await scaffoldAiAppInstructions(templateChoices);
}
outro("Aye Captain, godspeed with yar vibe coding \u{1FAE1}");
}
__name(init, "init");
async function main() {
try {
await init();
} catch (error) {
debug2("Full error details:", error);
console.error("\nerror: %s", error.message || error);
console.error("\n\n\u{1F635} Shiver me timbers!\n");
process.exit(1);
}
}
__name(main, "main");
main();