agent-rules-init 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +77 -23
- package/dist/core/i18n.d.ts +44 -0
- package/dist/core/i18n.js +176 -0
- package/dist/core/llm-bridge.d.ts +2 -1
- package/dist/core/llm-bridge.js +4 -4
- package/dist/core/prompt-engine.d.ts +3 -1
- package/dist/core/prompt-engine.js +19 -14
- package/dist/core/repo-facts.d.ts +20 -0
- package/dist/core/repo-facts.js +198 -0
- package/dist/core/scanner.js +26 -3
- package/dist/core/templates.d.ts +6 -4
- package/dist/core/templates.js +48 -23
- package/dist/core/types.d.ts +33 -2
- package/dist/core/writer.d.ts +1 -1
- package/dist/core/writer.js +3 -1
- package/dist/packs/cpp.js +33 -27
- package/dist/packs/csharp.js +32 -27
- package/dist/packs/dart.js +30 -31
- package/dist/packs/elixir.js +31 -27
- package/dist/packs/go.js +31 -27
- package/dist/packs/java.js +33 -27
- package/dist/packs/js-ts.js +42 -32
- package/dist/packs/kotlin.js +32 -27
- package/dist/packs/php.js +30 -27
- package/dist/packs/python.js +35 -27
- package/dist/packs/r.js +30 -27
- package/dist/packs/ruby.js +36 -27
- package/dist/packs/rust.js +33 -27
- package/dist/packs/scala.js +32 -27
- package/dist/packs/swift.js +31 -27
- package/package.json +3 -2
package/dist/cli.d.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { type WriteResult } from "./core/writer.js";
|
|
2
|
+
import { type Lang } from "./core/i18n.js";
|
|
2
3
|
import { type PromptFn } from "./core/prompt-engine.js";
|
|
3
4
|
import { type ExecFn } from "./core/llm-bridge.js";
|
|
4
5
|
export interface RunCliOptions {
|
|
5
6
|
promptFn?: PromptFn;
|
|
6
7
|
execFn?: ExecFn;
|
|
7
8
|
skipLlm?: boolean;
|
|
9
|
+
lang?: Lang;
|
|
8
10
|
}
|
|
9
11
|
export declare function runCli(rootPath: string, options?: RunCliOptions): Promise<WriteResult[]>;
|
|
12
|
+
export type CliAction = {
|
|
13
|
+
kind: "run";
|
|
14
|
+
lang?: Lang;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "help";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "version";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "invalid-lang";
|
|
21
|
+
value: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "unknown";
|
|
24
|
+
flag: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function resolveCliAction(argv: string[]): CliAction;
|
|
27
|
+
export declare function getVersion(): string;
|
|
10
28
|
export declare function main(): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import * as clack from "@clack/prompts";
|
|
2
3
|
import { scanRepo } from "./core/scanner.js";
|
|
3
4
|
import { writeGeneratedFiles } from "./core/writer.js";
|
|
4
|
-
import { renderClaudeMd, renderAgentsMd, renderCopilotInstructions, renderPromptFiles, } from "./core/templates.js";
|
|
5
|
-
import {
|
|
5
|
+
import { renderClaudeMd, renderAgentsMd, renderCopilotInstructions, renderPromptFiles, renderRepoFacts, } from "./core/templates.js";
|
|
6
|
+
import { buildRepoFacts } from "./core/repo-facts.js";
|
|
7
|
+
import { UI, detectLang } from "./core/i18n.js";
|
|
8
|
+
import { collectLowConfidenceQuestions, askQuestions, applyAnswers, makeDefaultPromptFn, hasInteractiveTty, } from "./core/prompt-engine.js";
|
|
6
9
|
import { detectAvailableAssistants, polishWithAssistant, defaultExecFn } from "./core/llm-bridge.js";
|
|
7
10
|
import { jsTsPack } from "./packs/js-ts.js";
|
|
8
11
|
import { pythonPack } from "./packs/python.js";
|
|
@@ -37,87 +40,138 @@ const ALL_PACKS = [
|
|
|
37
40
|
rPack,
|
|
38
41
|
];
|
|
39
42
|
export async function runCli(rootPath, options = {}) {
|
|
40
|
-
const promptFn = options.promptFn ?? defaultPromptFn;
|
|
41
43
|
const execFn = options.execFn ?? defaultExecFn;
|
|
44
|
+
const lang = options.lang ?? detectLang();
|
|
45
|
+
const promptFn = options.promptFn ?? makeDefaultPromptFn(lang);
|
|
46
|
+
const ui = UI[lang];
|
|
42
47
|
const signals = scanRepo(rootPath);
|
|
43
48
|
const rawDetections = ALL_PACKS.map((pack) => pack.detect(signals)).filter((d) => d !== null);
|
|
44
|
-
const questions = collectLowConfidenceQuestions(rawDetections);
|
|
49
|
+
const questions = collectLowConfidenceQuestions(rawDetections, lang);
|
|
45
50
|
const answers = await askQuestions(questions, promptFn);
|
|
46
51
|
const detections = applyAnswers(rawDetections, answers);
|
|
52
|
+
const facts = buildRepoFacts(signals, lang);
|
|
47
53
|
const entries = detections.map((detection) => {
|
|
48
54
|
const pack = ALL_PACKS.find((p) => p.id === detection.packId);
|
|
49
|
-
return { detection, ruleSet: pack.rules(detection) };
|
|
55
|
+
return { detection, ruleSet: pack.rules(detection, lang) };
|
|
50
56
|
});
|
|
51
57
|
const files = [];
|
|
52
58
|
if (entries.length > 0) {
|
|
53
|
-
files.push({ path: "CLAUDE.generated.md", content: renderClaudeMd(entries) });
|
|
54
|
-
files.push({ path: "AGENTS.generated.md", content: renderAgentsMd(entries) });
|
|
59
|
+
files.push({ path: "CLAUDE.generated.md", content: renderClaudeMd(entries, facts, lang) });
|
|
60
|
+
files.push({ path: "AGENTS.generated.md", content: renderAgentsMd(entries, facts, lang) });
|
|
55
61
|
files.push({
|
|
56
62
|
path: ".github/copilot-instructions.generated.md",
|
|
57
|
-
content: renderCopilotInstructions(entries),
|
|
63
|
+
content: renderCopilotInstructions(entries, facts, lang),
|
|
58
64
|
});
|
|
59
65
|
for (const detection of detections) {
|
|
60
66
|
const pack = ALL_PACKS.find((p) => p.id === detection.packId);
|
|
61
|
-
for (const file of renderPromptFiles(detection.packId, pack.promptTemplates(detection))) {
|
|
67
|
+
for (const file of renderPromptFiles(detection.packId, pack.promptTemplates(detection, lang))) {
|
|
62
68
|
files.push(file);
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
else {
|
|
73
|
+
const factsBlock = renderRepoFacts(facts, lang);
|
|
67
74
|
files.push({
|
|
68
75
|
path: "CLAUDE.generated.md",
|
|
69
|
-
content:
|
|
76
|
+
content: `# CLAUDE.md\n\n${ui.noStackFallback}\n` + (factsBlock ? `\n${factsBlock}\n` : ""),
|
|
70
77
|
});
|
|
71
78
|
}
|
|
72
79
|
if (!options.skipLlm && hasInteractiveTty()) {
|
|
73
80
|
const assistants = await detectAvailableAssistants(execFn);
|
|
74
81
|
if (assistants.length > 0) {
|
|
75
82
|
const chosenAssistant = assistants[0];
|
|
76
|
-
clack.log.info(
|
|
77
|
-
const usePolish = await clack.confirm({
|
|
78
|
-
message: `Se detectó ${chosenAssistant}. ¿Quieres que pula la redacción final?`,
|
|
79
|
-
});
|
|
83
|
+
clack.log.info(ui.polishDetected(chosenAssistant));
|
|
84
|
+
const usePolish = await clack.confirm({ message: ui.polishConfirm(chosenAssistant) });
|
|
80
85
|
if (usePolish === true) {
|
|
81
86
|
for (const file of files) {
|
|
82
|
-
file.content = await polishWithAssistant(chosenAssistant, file.content, execFn);
|
|
87
|
+
file.content = await polishWithAssistant(chosenAssistant, file.content, execFn, lang);
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
return writeGeneratedFiles(rootPath, files);
|
|
88
93
|
}
|
|
94
|
+
function isLang(value) {
|
|
95
|
+
return value === "es" || value === "en";
|
|
96
|
+
}
|
|
97
|
+
export function resolveCliAction(argv) {
|
|
98
|
+
let lang;
|
|
99
|
+
for (let i = 0; i < argv.length; i++) {
|
|
100
|
+
const arg = argv[i];
|
|
101
|
+
if (arg === "--help" || arg === "-h")
|
|
102
|
+
return { kind: "help" };
|
|
103
|
+
if (arg === "--version" || arg === "-v")
|
|
104
|
+
return { kind: "version" };
|
|
105
|
+
if (arg === "--lang" || arg.startsWith("--lang=")) {
|
|
106
|
+
const value = arg.startsWith("--lang=") ? arg.slice("--lang=".length) : argv[++i] ?? "";
|
|
107
|
+
if (!isLang(value))
|
|
108
|
+
return { kind: "invalid-lang", value };
|
|
109
|
+
lang = value;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
return { kind: "unknown", flag: arg };
|
|
113
|
+
}
|
|
114
|
+
return lang ? { kind: "run", lang } : { kind: "run" };
|
|
115
|
+
}
|
|
116
|
+
export function getVersion() {
|
|
117
|
+
// Works both from src/ (tests) and dist/ (published bin): ../package.json
|
|
118
|
+
// resolves to packages/cli/package.json in either layout.
|
|
119
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
120
|
+
return pkg.version;
|
|
121
|
+
}
|
|
89
122
|
export async function main() {
|
|
123
|
+
const action = resolveCliAction(process.argv.slice(2));
|
|
124
|
+
const lang = action.kind === "run" && action.lang ? action.lang : detectLang();
|
|
125
|
+
const ui = UI[lang];
|
|
126
|
+
if (action.kind === "help") {
|
|
127
|
+
console.log(ui.usage);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (action.kind === "version") {
|
|
131
|
+
console.log(getVersion());
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (action.kind === "unknown") {
|
|
135
|
+
console.error(`${ui.unknownOption(action.flag)}\n\n${ui.usage}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (action.kind === "invalid-lang") {
|
|
140
|
+
console.error(`${ui.invalidLang(action.value)}\n\n${ui.usage}`);
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
90
144
|
clack.intro("agent-rules-init");
|
|
91
145
|
if (!hasInteractiveTty()) {
|
|
92
|
-
console.warn(
|
|
93
|
-
"Continuando sin preguntas ni oferta de pulido con IA; se usarán los valores detectados.");
|
|
146
|
+
console.warn(ui.noTtyWarning);
|
|
94
147
|
}
|
|
95
148
|
try {
|
|
96
|
-
const results = await runCli(process.cwd());
|
|
149
|
+
const results = await runCli(process.cwd(), { lang });
|
|
97
150
|
const written = results.filter((r) => r.status === "written");
|
|
98
151
|
const failures = results.filter((r) => r.status === "error");
|
|
99
152
|
for (const result of results) {
|
|
100
153
|
if (result.status === "written") {
|
|
101
154
|
clack.log.success(result.path);
|
|
102
155
|
}
|
|
156
|
+
else if (result.status === "skipped") {
|
|
157
|
+
clack.log.info(ui.fileSkipped(result.path));
|
|
158
|
+
}
|
|
103
159
|
else {
|
|
104
160
|
clack.log.warn(`${result.path}: ${result.error}`);
|
|
105
161
|
}
|
|
106
162
|
}
|
|
107
163
|
if (written.length > 0) {
|
|
108
|
-
clack.outro(
|
|
109
|
-
'".generated" (ej. "CLAUDE.generated.md" → "CLAUDE.md") para activarlos — ' +
|
|
110
|
-
"tu asistente de IA solo lee el nombre final, no el generado.");
|
|
164
|
+
clack.outro(ui.outroWritten);
|
|
111
165
|
}
|
|
112
166
|
else {
|
|
113
|
-
clack.outro(
|
|
167
|
+
clack.outro(ui.outroNothing);
|
|
114
168
|
}
|
|
115
169
|
if (failures.length > 0) {
|
|
116
170
|
process.exitCode = 1;
|
|
117
171
|
}
|
|
118
172
|
}
|
|
119
173
|
catch (err) {
|
|
120
|
-
clack.log.error(
|
|
174
|
+
clack.log.error(ui.unexpectedError(err.message));
|
|
121
175
|
process.exitCode = 1;
|
|
122
176
|
}
|
|
123
177
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type Lang = "es" | "en";
|
|
2
|
+
export declare function detectLang(): Lang;
|
|
3
|
+
export declare function summarySentence(lang: Lang, language: string, framework?: string, parenthetical?: string): string;
|
|
4
|
+
export declare function runTestsConvention(lang: Lang, cmd: string): string;
|
|
5
|
+
export declare function reviewBody(lang: Lang, focus: string, framework: string): string;
|
|
6
|
+
export declare function refactorBody(lang: Lang, extra?: string): string;
|
|
7
|
+
export declare function testingBody(lang: Lang, runner: string): string;
|
|
8
|
+
export declare function unknownRunnerLabel(lang: Lang): string;
|
|
9
|
+
export declare function unknownFrameworkLabel(lang: Lang): string;
|
|
10
|
+
export interface UiTexts {
|
|
11
|
+
generatedHeader: string;
|
|
12
|
+
sections: {
|
|
13
|
+
commands: string;
|
|
14
|
+
structure: string;
|
|
15
|
+
ci: string;
|
|
16
|
+
conventions: string;
|
|
17
|
+
architecture: string;
|
|
18
|
+
};
|
|
19
|
+
andMore: (count: number, file?: string) => string;
|
|
20
|
+
noStackFallback: string;
|
|
21
|
+
question: (fieldLabel: string, language: string) => string;
|
|
22
|
+
fieldLabels: {
|
|
23
|
+
framework: string;
|
|
24
|
+
testRunner: string;
|
|
25
|
+
linter: string;
|
|
26
|
+
packageManager: string;
|
|
27
|
+
};
|
|
28
|
+
usage: string;
|
|
29
|
+
unknownOption: (flag: string) => string;
|
|
30
|
+
invalidLang: (value: string) => string;
|
|
31
|
+
noTtyWarning: string;
|
|
32
|
+
skippedQuestion: (message: string) => string;
|
|
33
|
+
polishDetected: (assistant: string) => string;
|
|
34
|
+
polishConfirm: (assistant: string) => string;
|
|
35
|
+
polishFailed: (assistant: string, error: string) => string;
|
|
36
|
+
polishPrompt: (content: string) => string;
|
|
37
|
+
fileSkipped: (path: string) => string;
|
|
38
|
+
outroWritten: string;
|
|
39
|
+
outroNothing: string;
|
|
40
|
+
unexpectedError: (message: string) => string;
|
|
41
|
+
cancelled: string;
|
|
42
|
+
dirNotes: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
export declare const UI: Record<Lang, UiTexts>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
export function detectLang() {
|
|
2
|
+
try {
|
|
3
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "";
|
|
4
|
+
return locale.toLowerCase().startsWith("es") ? "es" : "en";
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return "en";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function summarySentence(lang, language, framework, parenthetical) {
|
|
11
|
+
const paren = parenthetical ? ` (${parenthetical})` : "";
|
|
12
|
+
if (lang === "es")
|
|
13
|
+
return `Proyecto ${language}${framework ? ` con ${framework}` : ""}${paren}.`;
|
|
14
|
+
return `${language} project${framework ? ` using ${framework}` : ""}${paren}.`;
|
|
15
|
+
}
|
|
16
|
+
export function runTestsConvention(lang, cmd) {
|
|
17
|
+
return lang === "es"
|
|
18
|
+
? `Ejecuta los tests con ${cmd} antes de terminar una tarea.`
|
|
19
|
+
: `Run the tests with ${cmd} before finishing a task.`;
|
|
20
|
+
}
|
|
21
|
+
export function reviewBody(lang, focus, framework) {
|
|
22
|
+
// Con focus vacío la frase colapsa a "bugs y desviaciones" sin coma colgante.
|
|
23
|
+
const focusPart = focus ? `, ${focus}` : "";
|
|
24
|
+
return lang === "es"
|
|
25
|
+
? `Revisa el diff actual buscando bugs${focusPart} y desviaciones de las convenciones de ${framework}. Señala solo problemas concretos con línea de archivo.`
|
|
26
|
+
: `Review the current diff looking for bugs${focusPart} and deviations from ${framework} conventions. Point out only concrete issues with file and line.`;
|
|
27
|
+
}
|
|
28
|
+
export function refactorBody(lang, extra) {
|
|
29
|
+
const base = lang === "es"
|
|
30
|
+
? "Propón refactors que reduzcan duplicación y mejoren la legibilidad sin cambiar comportamiento observable."
|
|
31
|
+
: "Propose refactors that reduce duplication and improve readability without changing observable behavior.";
|
|
32
|
+
return extra ? `${base} ${extra}` : base;
|
|
33
|
+
}
|
|
34
|
+
export function testingBody(lang, runner) {
|
|
35
|
+
return lang === "es"
|
|
36
|
+
? `Escribe tests con ${runner} para el código señalado. Cubre el camino feliz y al menos un caso límite.`
|
|
37
|
+
: `Write tests with ${runner} for the highlighted code. Cover the happy path and at least one edge case.`;
|
|
38
|
+
}
|
|
39
|
+
export function unknownRunnerLabel(lang) {
|
|
40
|
+
return lang === "es" ? "el test runner del proyecto" : "the project's test runner";
|
|
41
|
+
}
|
|
42
|
+
export function unknownFrameworkLabel(lang) {
|
|
43
|
+
return lang === "es" ? "el framework del proyecto" : "the project's framework";
|
|
44
|
+
}
|
|
45
|
+
export const UI = {
|
|
46
|
+
es: {
|
|
47
|
+
generatedHeader: "Generado por agent-rules-init a partir de lo detectado en este repo.",
|
|
48
|
+
sections: {
|
|
49
|
+
commands: "Comandos del repo",
|
|
50
|
+
structure: "Estructura",
|
|
51
|
+
ci: "Lo que ejecuta CI (GitHub Actions)",
|
|
52
|
+
conventions: "Convenciones",
|
|
53
|
+
architecture: "Arquitectura",
|
|
54
|
+
},
|
|
55
|
+
andMore: (count, file) => (file ? `…y ${count} más en ${file}` : `…y ${count} más`),
|
|
56
|
+
noStackFallback: "No se detectó ningún stack conocido. Completa este archivo manualmente.",
|
|
57
|
+
question: (fieldLabel, language) => `No se pudo determinar ${fieldLabel} para ${language}. ¿Cuál usáis?`,
|
|
58
|
+
fieldLabels: {
|
|
59
|
+
framework: "el framework",
|
|
60
|
+
testRunner: "el test runner",
|
|
61
|
+
linter: "el linter",
|
|
62
|
+
packageManager: "el gestor de paquetes",
|
|
63
|
+
},
|
|
64
|
+
usage: `agent-rules-init — genera CLAUDE.md, AGENTS.md, copilot-instructions y prompts de review/refactor/testing a partir del stack detectado en tu repo.
|
|
65
|
+
|
|
66
|
+
Uso:
|
|
67
|
+
npx agent-rules-init escanea el directorio actual y genera los archivos *.generated.*
|
|
68
|
+
npx agent-rules-init --lang es fuerza el idioma del contenido (es|en); por defecto se detecta del sistema
|
|
69
|
+
npx agent-rules-init --help muestra esta ayuda
|
|
70
|
+
npx agent-rules-init --version muestra la versión
|
|
71
|
+
|
|
72
|
+
Los archivos se crean siempre con sufijo .generated y nunca sobrescriben nada existente:
|
|
73
|
+
revisa su contenido y quita el sufijo para activarlos.`,
|
|
74
|
+
unknownOption: (flag) => `Opción no reconocida: ${flag}`,
|
|
75
|
+
invalidLang: (value) => `Valor de --lang no válido: "${value}" (usa "es" o "en").`,
|
|
76
|
+
noTtyWarning: "No se detectó una terminal interactiva (esto pasa a veces en Git Bash en Windows). " +
|
|
77
|
+
"Continuando sin preguntas ni oferta de pulido con IA; se usarán los valores detectados.",
|
|
78
|
+
skippedQuestion: (message) => `No se detectó una terminal interactiva; se omite la pregunta "${message}" y se usa el valor detectado.`,
|
|
79
|
+
polishDetected: (assistant) => `${assistant} detectado — puede ayudar a pulir la redacción final.`,
|
|
80
|
+
polishConfirm: (assistant) => `Se detectó ${assistant}. ¿Quieres que pula la redacción final?`,
|
|
81
|
+
polishFailed: (assistant, error) => `No se pudo pulir el contenido con ${assistant}, se mantiene el original: ${error}`,
|
|
82
|
+
polishPrompt: (content) => `Pule la redacción del siguiente documento de instrucciones para un agente de IA, sin cambiar su significado ni estructura. Devuelve únicamente el documento pulido, sin comentarios ni explicaciones adicionales:\n\n${content}`,
|
|
83
|
+
fileSkipped: (path) => `${path}: ya existía, se conserva sin cambios.`,
|
|
84
|
+
outroWritten: "Revisa los archivos *.generated.* y, cuando estés conforme, quita el sufijo " +
|
|
85
|
+
'".generated" (ej. "CLAUDE.generated.md" → "CLAUDE.md") para activarlos — ' +
|
|
86
|
+
"tu asistente de IA solo lee el nombre final, no el generado.",
|
|
87
|
+
outroNothing: "No se generó ningún archivo nuevo.",
|
|
88
|
+
unexpectedError: (message) => `Fallo inesperado: ${message}`,
|
|
89
|
+
cancelled: "Operación cancelada.",
|
|
90
|
+
dirNotes: {
|
|
91
|
+
src: "código fuente",
|
|
92
|
+
lib: "código fuente",
|
|
93
|
+
tests: "tests",
|
|
94
|
+
test: "tests",
|
|
95
|
+
spec: "tests",
|
|
96
|
+
__tests__: "tests",
|
|
97
|
+
docs: "documentación",
|
|
98
|
+
doc: "documentación",
|
|
99
|
+
examples: "ejemplos",
|
|
100
|
+
scripts: "scripts auxiliares",
|
|
101
|
+
tools: "herramientas auxiliares",
|
|
102
|
+
migrations: "migraciones de base de datos",
|
|
103
|
+
benchmarks: "benchmarks",
|
|
104
|
+
".github": "workflows y configuración de GitHub",
|
|
105
|
+
public: "activos públicos",
|
|
106
|
+
static: "activos estáticos",
|
|
107
|
+
assets: "activos",
|
|
108
|
+
config: "configuración",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
en: {
|
|
112
|
+
generatedHeader: "Generated by agent-rules-init from what was detected in this repo.",
|
|
113
|
+
sections: {
|
|
114
|
+
commands: "Repo commands",
|
|
115
|
+
structure: "Structure",
|
|
116
|
+
ci: "What CI runs (GitHub Actions)",
|
|
117
|
+
conventions: "Conventions",
|
|
118
|
+
architecture: "Architecture",
|
|
119
|
+
},
|
|
120
|
+
andMore: (count, file) => (file ? `…and ${count} more in ${file}` : `…and ${count} more`),
|
|
121
|
+
noStackFallback: "No known stack was detected. Fill in this file manually.",
|
|
122
|
+
question: (fieldLabel, language) => `Couldn't determine ${fieldLabel} for ${language}. Which one do you use?`,
|
|
123
|
+
fieldLabels: {
|
|
124
|
+
framework: "the framework",
|
|
125
|
+
testRunner: "the test runner",
|
|
126
|
+
linter: "the linter",
|
|
127
|
+
packageManager: "the package manager",
|
|
128
|
+
},
|
|
129
|
+
usage: `agent-rules-init — generates CLAUDE.md, AGENTS.md, copilot-instructions and review/refactor/testing prompts from the stack detected in your repo.
|
|
130
|
+
|
|
131
|
+
Usage:
|
|
132
|
+
npx agent-rules-init scan the current directory and generate the *.generated.* files
|
|
133
|
+
npx agent-rules-init --lang en force the content language (es|en); defaults to the system locale
|
|
134
|
+
npx agent-rules-init --help show this help
|
|
135
|
+
npx agent-rules-init --version show the version
|
|
136
|
+
|
|
137
|
+
Files are always created with the .generated suffix and never overwrite anything:
|
|
138
|
+
review their content and drop the suffix to activate them.`,
|
|
139
|
+
unknownOption: (flag) => `Unknown option: ${flag}`,
|
|
140
|
+
invalidLang: (value) => `Invalid --lang value: "${value}" (use "es" or "en").`,
|
|
141
|
+
noTtyWarning: "No interactive terminal detected (this sometimes happens in Git Bash on Windows). " +
|
|
142
|
+
"Continuing without questions or the AI-polish offer; detected values will be used.",
|
|
143
|
+
skippedQuestion: (message) => `No interactive terminal detected; skipping the question "${message}" and using the detected value.`,
|
|
144
|
+
polishDetected: (assistant) => `${assistant} detected — it can help polish the final wording.`,
|
|
145
|
+
polishConfirm: (assistant) => `${assistant} was detected. Do you want it to polish the final wording?`,
|
|
146
|
+
polishFailed: (assistant, error) => `Couldn't polish the content with ${assistant}, keeping the original: ${error}`,
|
|
147
|
+
polishPrompt: (content) => `Polish the wording of the following instructions document for an AI agent, without changing its meaning or structure. Return only the polished document, with no extra comments or explanations:\n\n${content}`,
|
|
148
|
+
fileSkipped: (path) => `${path}: already existed, left unchanged.`,
|
|
149
|
+
outroWritten: "Review the *.generated.* files and, once you are happy with them, drop the " +
|
|
150
|
+
'".generated" suffix (e.g. "CLAUDE.generated.md" → "CLAUDE.md") to activate them — ' +
|
|
151
|
+
"your AI assistant only reads the final name, not the generated one.",
|
|
152
|
+
outroNothing: "No new files were generated.",
|
|
153
|
+
unexpectedError: (message) => `Unexpected failure: ${message}`,
|
|
154
|
+
cancelled: "Operation cancelled.",
|
|
155
|
+
dirNotes: {
|
|
156
|
+
src: "source code",
|
|
157
|
+
lib: "source code",
|
|
158
|
+
tests: "tests",
|
|
159
|
+
test: "tests",
|
|
160
|
+
spec: "tests",
|
|
161
|
+
__tests__: "tests",
|
|
162
|
+
docs: "documentation",
|
|
163
|
+
doc: "documentation",
|
|
164
|
+
examples: "examples",
|
|
165
|
+
scripts: "helper scripts",
|
|
166
|
+
tools: "helper tooling",
|
|
167
|
+
migrations: "database migrations",
|
|
168
|
+
benchmarks: "benchmarks",
|
|
169
|
+
".github": "GitHub workflows and configuration",
|
|
170
|
+
public: "public assets",
|
|
171
|
+
static: "static assets",
|
|
172
|
+
assets: "assets",
|
|
173
|
+
config: "configuration",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Lang } from "./i18n.js";
|
|
1
2
|
export type AssistantId = "claude" | "codex";
|
|
2
3
|
export interface ExecResult {
|
|
3
4
|
stdout: string;
|
|
@@ -6,4 +7,4 @@ export interface ExecResult {
|
|
|
6
7
|
export type ExecFn = (command: string, args: string[], stdin?: string) => Promise<ExecResult>;
|
|
7
8
|
export declare const defaultExecFn: ExecFn;
|
|
8
9
|
export declare function detectAvailableAssistants(execFn?: ExecFn): Promise<AssistantId[]>;
|
|
9
|
-
export declare function polishWithAssistant(assistant: AssistantId, content: string, execFn?: ExecFn): Promise<string>;
|
|
10
|
+
export declare function polishWithAssistant(assistant: AssistantId, content: string, execFn?: ExecFn, lang?: Lang): Promise<string>;
|
package/dist/core/llm-bridge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { UI } from "./i18n.js";
|
|
2
3
|
const VERSION_ARGS = {
|
|
3
4
|
claude: ["--version"],
|
|
4
5
|
codex: ["--version"],
|
|
@@ -34,14 +35,13 @@ export async function detectAvailableAssistants(execFn = defaultExecFn) {
|
|
|
34
35
|
}));
|
|
35
36
|
return results.filter((id) => id !== null);
|
|
36
37
|
}
|
|
37
|
-
export async function polishWithAssistant(assistant, content, execFn = defaultExecFn) {
|
|
38
|
-
const prompt = `Pule la redacción del siguiente documento de instrucciones para un agente de IA, sin cambiar su significado ni estructura. Devuelve únicamente el documento pulido, sin comentarios ni explicaciones adicionales:\n\n${content}`;
|
|
38
|
+
export async function polishWithAssistant(assistant, content, execFn = defaultExecFn, lang = "es") {
|
|
39
39
|
try {
|
|
40
|
-
const result = await execFn(assistant, ["-p"],
|
|
40
|
+
const result = await execFn(assistant, ["-p"], UI[lang].polishPrompt(content));
|
|
41
41
|
return result.stdout.trim() || content;
|
|
42
42
|
}
|
|
43
43
|
catch (err) {
|
|
44
|
-
console.warn(
|
|
44
|
+
console.warn(UI[lang].polishFailed(assistant, err.message));
|
|
45
45
|
return content;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Lang } from "./i18n.js";
|
|
1
2
|
import type { DetectionResult } from "./types.js";
|
|
2
3
|
export type QuestionField = "framework" | "testRunner" | "linter" | "packageManager";
|
|
3
4
|
export interface Question {
|
|
@@ -5,9 +6,10 @@ export interface Question {
|
|
|
5
6
|
field: QuestionField;
|
|
6
7
|
message: string;
|
|
7
8
|
}
|
|
8
|
-
export declare function collectLowConfidenceQuestions(detections: DetectionResult[]): Question[];
|
|
9
|
+
export declare function collectLowConfidenceQuestions(detections: DetectionResult[], lang: Lang): Question[];
|
|
9
10
|
export type PromptFn = (message: string) => Promise<string>;
|
|
10
11
|
export declare function hasInteractiveTty(): boolean;
|
|
12
|
+
export declare function makeDefaultPromptFn(lang: Lang): PromptFn;
|
|
11
13
|
export declare const defaultPromptFn: PromptFn;
|
|
12
14
|
export declare function askQuestions(questions: Question[], promptFn?: PromptFn): Promise<Record<string, string>>;
|
|
13
15
|
export declare function applyAnswers(detections: DetectionResult[], answers: Record<string, string>): DetectionResult[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as clack from "@clack/prompts";
|
|
2
|
+
import { UI, detectLang } from "./i18n.js";
|
|
2
3
|
const FIELDS = ["framework", "testRunner", "linter", "packageManager"];
|
|
3
|
-
export function collectLowConfidenceQuestions(detections) {
|
|
4
|
+
export function collectLowConfidenceQuestions(detections, lang) {
|
|
5
|
+
const ui = UI[lang];
|
|
4
6
|
const questions = [];
|
|
5
7
|
for (const detection of detections) {
|
|
6
8
|
for (const field of FIELDS) {
|
|
@@ -9,7 +11,7 @@ export function collectLowConfidenceQuestions(detections) {
|
|
|
9
11
|
questions.push({
|
|
10
12
|
packId: detection.packId,
|
|
11
13
|
field,
|
|
12
|
-
message:
|
|
14
|
+
message: ui.question(ui.fieldLabels[field], detection.language),
|
|
13
15
|
});
|
|
14
16
|
}
|
|
15
17
|
}
|
|
@@ -19,18 +21,21 @@ export function collectLowConfidenceQuestions(detections) {
|
|
|
19
21
|
export function hasInteractiveTty() {
|
|
20
22
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
21
23
|
}
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
clack.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
export function makeDefaultPromptFn(lang) {
|
|
25
|
+
return async (message) => {
|
|
26
|
+
if (!hasInteractiveTty()) {
|
|
27
|
+
console.warn(UI[lang].skippedQuestion(message));
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
const answer = await clack.text({ message });
|
|
31
|
+
if (clack.isCancel(answer)) {
|
|
32
|
+
clack.cancel(UI[lang].cancelled);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
return answer;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export const defaultPromptFn = (message) => makeDefaultPromptFn(detectLang())(message);
|
|
34
39
|
export async function askQuestions(questions, promptFn = defaultPromptFn) {
|
|
35
40
|
const answers = {};
|
|
36
41
|
for (const question of questions) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Lang } from "./i18n.js";
|
|
2
|
+
import type { CiCommand, CommandEntry, CommandSource, DirEntry, RepoFacts, RepoSignals } from "./types.js";
|
|
3
|
+
export declare function extractNpmCommands(signals: RepoSignals): CommandEntry[];
|
|
4
|
+
export declare function extractMakeTargets(signals: RepoSignals): CommandEntry[];
|
|
5
|
+
export declare function extractMixAliases(signals: RepoSignals): CommandEntry[];
|
|
6
|
+
export declare function extractToxEnvs(signals: RepoSignals): CommandEntry[];
|
|
7
|
+
export declare function extractComposerCommands(signals: RepoSignals): CommandEntry[];
|
|
8
|
+
export declare function filterCommands(entries: CommandEntry[]): {
|
|
9
|
+
kept: CommandEntry[];
|
|
10
|
+
omitted: {
|
|
11
|
+
source: CommandSource;
|
|
12
|
+
count: number;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
export declare function extractCiCommands(signals: RepoSignals): {
|
|
16
|
+
commands: CiCommand[];
|
|
17
|
+
omittedCount: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function extractStructure(signals: RepoSignals, lang: Lang): DirEntry[];
|
|
20
|
+
export declare function buildRepoFacts(signals: RepoSignals, lang: Lang): RepoFacts;
|