agent-rules-init 0.3.0 → 0.4.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.
@@ -1,4 +1,5 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
+ import { canonicalOf } from "../core/canonical-commands.js";
2
3
  const FRAMEWORKS = {
3
4
  next: "next",
4
5
  "@nestjs/core": "nestjs",
@@ -26,34 +27,66 @@ function detectFromDeps(deps, table) {
26
27
  }
27
28
  return undefined;
28
29
  }
30
+ function detectPackageManagerFromCi(signals) {
31
+ const managers = new Set();
32
+ for (const workflow of signals.githubWorkflows ?? []) {
33
+ for (const rawLine of workflow.content.split(/\r?\n/)) {
34
+ const command = rawLine.trim().replace(/^(?:-\s*)?run:\s*(?:[|>]\s*)?/, "").trim();
35
+ const manager = /^(npm|pnpm|yarn|bun)\b/.exec(command)?.[1];
36
+ if (manager)
37
+ managers.add(manager);
38
+ }
39
+ }
40
+ return managers.size === 1 ? { value: [...managers][0], confidence: "high" } : undefined;
41
+ }
29
42
  function detect(signals) {
30
43
  if (!signals.packageJson)
31
44
  return null;
32
45
  const allDeps = { ...signals.packageJson.dependencies, ...signals.packageJson.devDependencies };
46
+ const hasAnyFileNamed = (name) => signals.files.some((file) => file.split(/[\\/]/).pop() === name);
47
+ const frameworkSource = signals.packageJson.name
48
+ ? FRAMEWORKS[signals.packageJson.name]
49
+ : undefined;
33
50
  const framework = detectFromDeps(allDeps, FRAMEWORKS) ?? {
34
51
  value: "none",
35
- confidence: "low",
52
+ confidence: frameworkSource ? "high" : "low",
36
53
  };
37
54
  const testRunner = detectFromDeps(allDeps, TEST_RUNNERS) ?? {
38
55
  value: "unknown",
39
56
  confidence: "low",
40
57
  };
41
58
  const linter = detectFromDeps(allDeps, LINTERS) ?? { value: "unknown", confidence: "low" };
42
- const packageManager = signals.hasFile("pnpm-lock.yaml")
43
- ? { value: "pnpm", confidence: "high" }
44
- : signals.hasFile("yarn.lock")
45
- ? { value: "yarn", confidence: "high" }
46
- : signals.hasFile("package-lock.json")
47
- ? { value: "npm", confidence: "high" }
48
- : { value: "npm", confidence: "low" };
49
- const usesTypeScript = Boolean(allDeps.typescript) || signals.hasFile("tsconfig.json");
50
- const moduleFormat = signals.packageJson.moduleType;
59
+ const packageManager = signals.packageJson.packageManager
60
+ ? { value: signals.packageJson.packageManager, confidence: "high" }
61
+ : signals.hasFile("bun.lock") || signals.hasFile("bun.lockb")
62
+ ? { value: "bun", confidence: "high" }
63
+ : signals.hasFile("pnpm-lock.yaml")
64
+ ? { value: "pnpm", confidence: "high" }
65
+ : signals.hasFile("yarn.lock")
66
+ ? { value: "yarn", confidence: "high" }
67
+ : signals.hasFile("package-lock.json") || signals.hasFile("npm-shrinkwrap.json")
68
+ ? { value: "npm", confidence: "high" }
69
+ : hasAnyFileNamed("bun.lock") || hasAnyFileNamed("bun.lockb")
70
+ ? { value: "bun", confidence: "high" }
71
+ : hasAnyFileNamed("pnpm-lock.yaml")
72
+ ? { value: "pnpm", confidence: "high" }
73
+ : hasAnyFileNamed("yarn.lock")
74
+ ? { value: "yarn", confidence: "high" }
75
+ : hasAnyFileNamed("package-lock.json") || hasAnyFileNamed("npm-shrinkwrap.json")
76
+ ? { value: "npm", confidence: "high" }
77
+ : detectPackageManagerFromCi(signals) ?? { value: "npm", confidence: "low" };
78
+ const usesTypeScript = Boolean(allDeps.typescript) || signals.hasFile("tsconfig.json") || hasAnyFileNamed("tsconfig.json");
79
+ const moduleTypes = new Set((signals.packageJsons?.length ? signals.packageJsons : [signals.packageJson]).map((p) => p.moduleType));
80
+ // Mixed ESM/CommonJS workspaces should not receive a repo-wide rule that is wrong
81
+ // for half of the packages.
82
+ const moduleFormat = moduleTypes.size === 1 ? [...moduleTypes][0] : undefined;
51
83
  return {
52
84
  packId: "js-ts",
53
85
  language: usesTypeScript ? "TypeScript" : "JavaScript",
54
86
  framework,
55
87
  testRunner,
56
88
  linter,
89
+ frameworkSource,
57
90
  packageManager,
58
91
  usesTypeScript,
59
92
  moduleFormat,
@@ -68,7 +101,8 @@ const TEXTS = {
68
101
  "Mantén los componentes/módulos pequeños y con una responsabilidad clara.",
69
102
  "Coloca los tests junto al código que prueban o en un directorio `test/` espejo, según lo que ya use el repo.",
70
103
  ],
71
- reviewFocus: "errores de tipado, condiciones de carrera en async/await",
104
+ reviewFocusTs: "errores de tipado, condiciones de carrera en async/await",
105
+ reviewFocusJs: "condiciones de carrera en async/await",
72
106
  refactorExtra: "Respeta los tipos existentes.",
73
107
  },
74
108
  en: {
@@ -79,33 +113,102 @@ const TEXTS = {
79
113
  "Keep components/modules small and single-purpose.",
80
114
  "Place tests next to the code they cover or in a mirrored `test/` directory, following what the repo already uses.",
81
115
  ],
82
- reviewFocus: "typing errors, async/await race conditions",
116
+ reviewFocusTs: "typing errors, async/await race conditions",
117
+ reviewFocusJs: "async/await race conditions",
83
118
  refactorExtra: "Respect the existing types.",
84
119
  },
85
120
  };
86
- function rules(detection, lang) {
121
+ const FRAMEWORK_RISKS = {
122
+ express: {
123
+ es: "Presta especial atención al flujo de middleware, la propagación de errores con `next(err)` y el ciclo de vida de la respuesta.",
124
+ en: "Pay special attention to middleware control flow, error propagation through `next(err)` and response lifecycle handling.",
125
+ },
126
+ react: {
127
+ es: "Presta especial atención a las dependencias de hooks (`useEffect`), renders innecesarios y estado derivado.",
128
+ en: "Pay special attention to hook dependencies (`useEffect`), unnecessary re-renders and derived state.",
129
+ },
130
+ next: {
131
+ es: "Presta especial atención a la frontera servidor/cliente (`use client`), el data fetching y el caché de rutas.",
132
+ en: "Pay special attention to the server/client boundary (`use client`), data fetching and route caching.",
133
+ },
134
+ nestjs: {
135
+ es: "Presta especial atención a los scopes de providers, la inyección de dependencias y los pipes de validación.",
136
+ en: "Pay special attention to provider scopes, dependency injection and validation pipes.",
137
+ },
138
+ fastify: {
139
+ es: "Presta especial atención al ciclo de vida de plugins, la encapsulación y los schemas de validación.",
140
+ en: "Pay special attention to plugin lifecycle, encapsulation and validation schemas.",
141
+ },
142
+ };
143
+ function rules(detection, lang, ctx) {
87
144
  const t = TEXTS[lang];
88
145
  const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
89
146
  const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
147
+ const testCmd = canonicalOf(ctx, "test", "js-ts")?.command ?? runner;
90
148
  const conventions = [];
91
149
  if (detection.usesTypeScript)
92
150
  conventions.push(t.tsStrict);
93
- conventions.push(runTestsConvention(lang, runner ?? unknownRunnerLabel(lang)));
94
- conventions.push(detection.moduleFormat === "module" ? t.esModules : t.commonJs);
151
+ conventions.push(runTestsConvention(lang, testCmd));
152
+ if (detection.moduleFormat) {
153
+ conventions.push(detection.moduleFormat === "module" ? t.esModules : t.commonJs);
154
+ }
95
155
  return {
96
- summary: summarySentence(lang, detection.usesTypeScript ? "TypeScript" : "JavaScript", framework),
156
+ summary: detection.frameworkSource
157
+ ? lang === "es"
158
+ ? `Repositorio ${detection.usesTypeScript ? "TypeScript" : "JavaScript"} que implementa ${detection.frameworkSource}.`
159
+ : `${detection.usesTypeScript ? "TypeScript" : "JavaScript"} repository implementing ${detection.frameworkSource}.`
160
+ : summarySentence(lang, detection.usesTypeScript ? "TypeScript" : "JavaScript", framework),
97
161
  conventions,
98
162
  architectureNotes: t.arch,
99
163
  };
100
164
  }
101
- function promptTemplates(detection, lang) {
165
+ function promptTemplates(detection, lang, ctx) {
102
166
  const t = TEXTS[lang];
103
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
104
- const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner.value : unknownRunnerLabel(lang);
167
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
168
+ const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
169
+ const focus = detection.usesTypeScript ? t.reviewFocusTs : t.reviewFocusJs;
170
+ const test = canonicalOf(ctx, "test", "js-ts");
171
+ const lint = canonicalOf(ctx, "lint", "js-ts");
172
+ const testDirs = ctx?.facts.testDirs ?? [];
173
+ const es = lang === "es";
174
+ const reviewParts = [];
175
+ const moduleLabel = detection.moduleFormat === "commonjs" ? "CommonJS" : detection.moduleFormat === "module" ? "ESM" : undefined;
176
+ reviewParts.push(es
177
+ ? `Revisa el diff actual contra las convenciones${moduleLabel ? ` ${moduleLabel}` : ""} de este repositorio${framework ? ` (${framework})` : ""}.`
178
+ : `Review the current diff against this repository's${moduleLabel ? ` ${moduleLabel}` : ""} conventions${framework ? ` (${framework})` : ""}.`);
179
+ if (test || lint) {
180
+ const commands = [test, lint].filter((c) => c !== undefined);
181
+ const list = commands.map((c) => `\`${c.command}\``).join(es ? " y " : " and ");
182
+ reviewParts.push(es ? `Ejecuta ${list} antes de dar por buena la revisión.` : `Run ${list} before approving the review.`);
183
+ }
184
+ const risk = FRAMEWORK_RISKS[framework ?? detection.frameworkSource ?? ""]?.[lang];
185
+ if (risk)
186
+ reviewParts.push(risk);
187
+ reviewParts.push(es ? `Busca también bugs: ${focus}.` : `Also look for bugs: ${focus}.`);
188
+ if (testDirs.length > 0) {
189
+ const dirs = testDirs.map((d) => `\`${d}\``).join(", ");
190
+ reviewParts.push(es ? `Los tests viven en ${dirs}.` : `Tests live under ${dirs}.`);
191
+ }
192
+ reviewParts.push(es
193
+ ? "Señala solo hallazgos concretos con archivo y línea."
194
+ : "Report only concrete findings with file and line references.");
195
+ const testingParts = [];
196
+ testingParts.push(testingBody(lang, runner));
197
+ if (test) {
198
+ testingParts.push(es ? `Verifica la suite con \`${test.command}\` antes de terminar.` : `Verify the suite with \`${test.command}\` before finishing.`);
199
+ }
200
+ if (testDirs.length > 0) {
201
+ const dirs = testDirs.map((d) => `\`${d}\``).join(", ");
202
+ testingParts.push(es ? `Coloca los tests nuevos en ${dirs}.` : `Place new tests under ${dirs}.`);
203
+ }
105
204
  return [
106
- { id: "review", title: "Code Review (JS/TS)", body: reviewBody(lang, t.reviewFocus, framework) },
107
- { id: "refactor", title: "Refactor (JS/TS)", body: refactorBody(lang, t.refactorExtra) },
108
- { id: "testing", title: "Testing (JS/TS)", body: testingBody(lang, runner) },
205
+ {
206
+ id: "review",
207
+ title: "Code Review (JS/TS)",
208
+ body: ctx ? reviewParts.join(" ") : reviewBody(lang, focus, framework),
209
+ },
210
+ { id: "refactor", title: "Refactor (JS/TS)", body: refactorBody(lang, detection.usesTypeScript ? t.refactorExtra : undefined) },
211
+ { id: "testing", title: "Testing (JS/TS)", body: ctx ? testingParts.join(" ") : testingBody(lang, runner) },
109
212
  ];
110
213
  }
111
214
  export const jsTsPack = { id: "js-ts", detect, rules, promptTemplates };
@@ -1,13 +1,17 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  function detect(signals) {
3
- const source = signals.buildGradle;
3
+ const gradleSource = signals.buildGradle && /\bkotlin\b/i.test(signals.buildGradle)
4
+ ? signals.buildGradle
5
+ : undefined;
6
+ const mavenSource = signals.pomXml && /\bkotlin\b/i.test(signals.pomXml)
7
+ ? signals.pomXml
8
+ : undefined;
9
+ const source = gradleSource ?? mavenSource;
4
10
  if (!source)
5
11
  return null;
6
12
  // Modern Gradle projects often declare the Kotlin plugin via a version catalog alias
7
13
  // (`alias(libs.plugins.kotlin.android)`) rather than the literal `kotlin("jvm")` or
8
14
  // `org.jetbrains.kotlin` plugin id, so a plain word-boundary check is more robust.
9
- if (!/\bkotlin\b/i.test(source))
10
- return null;
11
15
  const framework = /ktor/i.test(source)
12
16
  ? { value: "ktor", confidence: "high" }
13
17
  : /com\.android\.application|com\.android\.library/i.test(source)
@@ -25,7 +29,13 @@ function detect(signals) {
25
29
  language: "Kotlin",
26
30
  framework,
27
31
  testRunner,
28
- packageManager: { value: "gradle", confidence: "high" },
32
+ packageManager: mavenSource && !gradleSource
33
+ ? signals.hasFile("mvnw") || signals.hasFile("mvnw.cmd")
34
+ ? { value: "maven wrapper", confidence: "high" }
35
+ : { value: "maven", confidence: "high" }
36
+ : signals.hasFile("gradlew") || signals.hasFile("gradlew.bat")
37
+ ? { value: "gradle wrapper", confidence: "high" }
38
+ : { value: "gradle", confidence: "high" },
29
39
  };
30
40
  }
31
41
  const TEXTS = {
@@ -52,15 +62,25 @@ function rules(detection, lang) {
52
62
  const t = TEXTS[lang];
53
63
  const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
54
64
  return {
55
- summary: summarySentence(lang, "Kotlin", framework, "gradle"),
56
- conventions: [t.style, runTestsConvention(lang, "`gradle test`"), t.nullsafety],
65
+ summary: summarySentence(lang, "Kotlin", framework, detection.packageManager?.value),
66
+ conventions: [
67
+ t.style,
68
+ runTestsConvention(lang, detection.packageManager?.value === "maven wrapper"
69
+ ? "`./mvnw test`"
70
+ : detection.packageManager?.value === "maven"
71
+ ? "`mvn test`"
72
+ : detection.packageManager?.value === "gradle wrapper"
73
+ ? "`./gradlew test`"
74
+ : "`gradle test`"),
75
+ t.nullsafety,
76
+ ],
57
77
  architectureNotes: t.arch,
58
78
  };
59
79
  }
60
80
  function promptTemplates(detection, lang) {
61
81
  const t = TEXTS[lang];
62
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
63
- const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner.value : unknownRunnerLabel(lang);
82
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
83
+ const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
64
84
  return [
65
85
  { id: "review", title: "Code Review (Kotlin)", body: reviewBody(lang, t.reviewFocus, framework) },
66
86
  { id: "refactor", title: "Refactor (Kotlin)", body: refactorBody(lang) },
package/dist/packs/php.js CHANGED
@@ -1,4 +1,4 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  const FRAMEWORKS = {
3
3
  "laravel/framework": "laravel",
4
4
  "symfony/symfony": "symfony",
@@ -47,7 +47,7 @@ const TEXTS = {
47
47
  function rules(detection, lang) {
48
48
  const t = TEXTS[lang];
49
49
  const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
50
- const testCmd = detection.testRunner?.value === "phpunit" ? "vendor/bin/phpunit" : unknownRunnerLabel(lang);
50
+ const testCmd = detection.testRunner?.value === "phpunit" ? "vendor/bin/phpunit" : undefined;
51
51
  return {
52
52
  summary: summarySentence(lang, "PHP", framework, "composer"),
53
53
  conventions: [t.style, runTestsConvention(lang, testCmd), t.deps],
@@ -55,8 +55,8 @@ function rules(detection, lang) {
55
55
  };
56
56
  }
57
57
  function promptTemplates(detection, lang) {
58
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
59
- const runner = detection.testRunner?.value === "phpunit" ? "PHPUnit" : unknownRunnerLabel(lang);
58
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
59
+ const runner = detection.testRunner?.value === "phpunit" ? "PHPUnit" : undefined;
60
60
  return [
61
61
  { id: "review", title: "Code Review (PHP)", body: reviewBody(lang, "", framework) },
62
62
  { id: "refactor", title: "Refactor (PHP)", body: refactorBody(lang) },
@@ -1,4 +1,5 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
+ import { canonicalOf } from "../core/canonical-commands.js";
2
3
  const FRAMEWORKS = [
3
4
  ["fastapi", "fastapi"],
4
5
  ["django", "django"],
@@ -35,21 +36,36 @@ function extractPyprojectDependencySections(pyproject) {
35
36
  sections.push(...poetryDepsBlocks);
36
37
  return sections.length > 0 ? sections.join("\n") : pyproject;
37
38
  }
39
+ function frameworkSourceProject(pyproject) {
40
+ const projectBlock = pyproject.match(/(?:^|\n)\[project\]([\s\S]*?)(?:\n\[|$)/)?.[1];
41
+ const name = projectBlock?.match(/(?:^|\n)\s*name\s*=\s*["']([^"']+)["']/i)?.[1].toLowerCase();
42
+ return FRAMEWORKS.find(([framework]) => framework === name)?.[1];
43
+ }
38
44
  function detect(signals) {
39
45
  const source = signals.pyprojectToml ?? signals.requirementsTxt ?? signals.environmentYml;
40
46
  if (!source)
41
47
  return null;
42
48
  const searchText = signals.pyprojectToml ? extractPyprojectDependencySections(signals.pyprojectToml) : source;
43
- const framework = findIn(searchText, FRAMEWORKS) ?? { value: "none", confidence: "low" };
49
+ const frameworkSource = signals.pyprojectToml
50
+ ? frameworkSourceProject(signals.pyprojectToml)
51
+ : undefined;
52
+ const framework = findIn(searchText, FRAMEWORKS) ?? {
53
+ value: "none",
54
+ confidence: frameworkSource
55
+ ? "high"
56
+ : "low",
57
+ };
44
58
  const testRunner = findIn(searchText, TEST_RUNNERS) ?? { value: "unknown", confidence: "low" };
45
59
  const packageManager = signals.environmentYml
46
60
  ? { value: "conda", confidence: "high" }
47
- : signals.hasFile("poetry.lock")
48
- ? { value: "poetry", confidence: "high" }
49
- : signals.pyprojectToml
50
- ? { value: "pip (pyproject.toml)", confidence: "low" }
51
- : { value: "pip", confidence: "low" };
52
- return { packId: "python", language: "Python", framework, testRunner, packageManager };
61
+ : signals.hasFile("uv.lock")
62
+ ? { value: "uv", confidence: "high" }
63
+ : signals.hasFile("poetry.lock")
64
+ ? { value: "poetry", confidence: "high" }
65
+ : signals.pyprojectToml
66
+ ? { value: "pip (pyproject.toml)", confidence: "low" }
67
+ : { value: "pip", confidence: "low" };
68
+ return { packId: "python", language: "Python", framework, frameworkSource, testRunner, packageManager };
53
69
  }
54
70
  const TEXTS = {
55
71
  es: {
@@ -73,24 +89,73 @@ const TEXTS = {
73
89
  refactorExtra: "Respect the existing type hints.",
74
90
  },
75
91
  };
76
- function rules(detection, lang) {
92
+ const FRAMEWORK_RISKS = {
93
+ flask: {
94
+ es: "Presta especial atención al contexto de aplicación/petición, los blueprints y el manejo de errores HTTP.",
95
+ en: "Pay special attention to application/request context, blueprints and HTTP error handling.",
96
+ },
97
+ django: {
98
+ es: "Presta especial atención a migraciones pendientes, consultas N+1 del ORM y validación en forms/serializers.",
99
+ en: "Pay special attention to pending migrations, ORM N+1 queries and validation in forms/serializers.",
100
+ },
101
+ fastapi: {
102
+ es: "Presta especial atención a los modelos Pydantic, las dependencias async y los códigos de respuesta declarados.",
103
+ en: "Pay special attention to Pydantic models, async dependencies and declared response codes.",
104
+ },
105
+ };
106
+ function rules(detection, lang, ctx) {
77
107
  const t = TEXTS[lang];
78
108
  const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
79
109
  const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
110
+ const testCmd = canonicalOf(ctx, "test", "python")?.command ?? runner;
80
111
  return {
81
- summary: summarySentence(lang, "Python", framework),
82
- conventions: [t.style, runTestsConvention(lang, runner ?? unknownRunnerLabel(lang)), t.deps],
112
+ summary: detection.frameworkSource
113
+ ? lang === "es"
114
+ ? `Repositorio Python que implementa ${detection.frameworkSource}.`
115
+ : `Python repository implementing ${detection.frameworkSource}.`
116
+ : summarySentence(lang, "Python", framework),
117
+ conventions: [t.style, runTestsConvention(lang, testCmd), t.deps],
83
118
  architectureNotes: t.arch,
84
119
  };
85
120
  }
86
- function promptTemplates(detection, lang) {
121
+ function promptTemplates(detection, lang, ctx) {
87
122
  const t = TEXTS[lang];
88
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
89
- const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner.value : unknownRunnerLabel(lang);
123
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
124
+ const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
125
+ const test = canonicalOf(ctx, "test", "python");
126
+ const testDirs = ctx?.facts.testDirs ?? [];
127
+ const hasTox = ctx?.facts.commands.some((c) => c.source === "tox") ?? false;
128
+ const es = lang === "es";
129
+ const reviewParts = [];
130
+ reviewParts.push(es
131
+ ? `Revisa el diff actual contra las convenciones Python de este repositorio${framework ? ` (${framework})` : ""}.`
132
+ : `Review the current diff against this repository's Python conventions${framework ? ` (${framework})` : ""}.`);
133
+ if (test) {
134
+ reviewParts.push(es ? `Ejecuta \`${test.command}\` antes de dar por buena la revisión.` : `Run \`${test.command}\` before approving the review.`);
135
+ }
136
+ if (hasTox) {
137
+ reviewParts.push(es ? "La matriz completa de entornos se ejecuta con tox (`tox.ini`)." : "The full environment matrix runs through tox (`tox.ini`).");
138
+ }
139
+ const risk = FRAMEWORK_RISKS[framework ?? detection.frameworkSource ?? ""]?.[lang];
140
+ if (risk)
141
+ reviewParts.push(risk);
142
+ reviewParts.push(es ? `Busca también bugs: ${t.reviewFocus}.` : `Also look for bugs: ${t.reviewFocus}.`);
143
+ if (testDirs.length > 0) {
144
+ const dirs = testDirs.map((d) => `\`${d}\``).join(", ");
145
+ reviewParts.push(es ? `Los tests viven en ${dirs}.` : `Tests live under ${dirs}.`);
146
+ }
147
+ reviewParts.push(es ? "Señala solo hallazgos concretos con archivo y línea." : "Report only concrete findings with file and line references.");
148
+ const testingParts = [testingBody(lang, runner)];
149
+ if (test)
150
+ testingParts.push(es ? `Verifica la suite con \`${test.command}\` antes de terminar.` : `Verify the suite with \`${test.command}\` before finishing.`);
151
+ if (testDirs.length > 0) {
152
+ const dirs = testDirs.map((d) => `\`${d}\``).join(", ");
153
+ testingParts.push(es ? `Coloca los tests nuevos en ${dirs}.` : `Place new tests under ${dirs}.`);
154
+ }
90
155
  return [
91
- { id: "review", title: "Code Review (Python)", body: reviewBody(lang, t.reviewFocus, framework) },
156
+ { id: "review", title: "Code Review (Python)", body: ctx ? reviewParts.join(" ") : reviewBody(lang, t.reviewFocus, framework) },
92
157
  { id: "refactor", title: "Refactor (Python)", body: refactorBody(lang, t.refactorExtra) },
93
- { id: "testing", title: "Testing (Python)", body: testingBody(lang, runner) },
158
+ { id: "testing", title: "Testing (Python)", body: ctx ? testingParts.join(" ") : testingBody(lang, runner) },
94
159
  ];
95
160
  }
96
161
  export const pythonPack = { id: "python", detect, rules, promptTemplates };
package/dist/packs/r.js CHANGED
@@ -1,4 +1,4 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  const FRAMEWORKS = [
3
3
  ["shiny", "shiny"],
4
4
  ["plumber", "plumber"],
@@ -44,7 +44,7 @@ const TEXTS = {
44
44
  function rules(detection, lang) {
45
45
  const t = TEXTS[lang];
46
46
  const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
47
- const testCmd = detection.testRunner?.value === "testthat" ? 'testthat::test_dir("tests")' : unknownRunnerLabel(lang);
47
+ const testCmd = detection.testRunner?.value === "testthat" ? 'testthat::test_dir("tests")' : undefined;
48
48
  return {
49
49
  summary: summarySentence(lang, "R", framework, detection.packageManager?.value),
50
50
  conventions: [t.style, runTestsConvention(lang, testCmd), t.deps],
@@ -52,8 +52,8 @@ function rules(detection, lang) {
52
52
  };
53
53
  }
54
54
  function promptTemplates(detection, lang) {
55
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
56
- const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner.value : unknownRunnerLabel(lang);
55
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
56
+ const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
57
57
  return [
58
58
  { id: "review", title: "Code Review (R)", body: reviewBody(lang, "", framework) },
59
59
  { id: "refactor", title: "Refactor (R)", body: refactorBody(lang) },
@@ -1,4 +1,4 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  function detect(signals) {
3
3
  const source = signals.gemfile;
4
4
  if (!source)
@@ -40,25 +40,25 @@ const TEXTS = {
40
40
  ],
41
41
  },
42
42
  };
43
- function testCommand(detection, lang) {
43
+ function testCommand(detection) {
44
44
  if (detection.testRunner?.value === "rspec")
45
45
  return "bundle exec rspec";
46
46
  if (detection.testRunner?.value === "minitest")
47
47
  return "bundle exec rake test";
48
- return unknownRunnerLabel(lang);
48
+ return undefined;
49
49
  }
50
50
  function rules(detection, lang) {
51
51
  const t = TEXTS[lang];
52
52
  const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
53
53
  return {
54
54
  summary: summarySentence(lang, "Ruby", framework, "bundler"),
55
- conventions: [t.style, runTestsConvention(lang, testCommand(detection, lang)), t.deps],
55
+ conventions: [t.style, runTestsConvention(lang, testCommand(detection)), t.deps],
56
56
  architectureNotes: t.arch,
57
57
  };
58
58
  }
59
59
  function promptTemplates(detection, lang) {
60
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
61
- const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner.value : unknownRunnerLabel(lang);
60
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
61
+ const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
62
62
  return [
63
63
  { id: "review", title: "Code Review (Ruby)", body: reviewBody(lang, "", framework) },
64
64
  { id: "refactor", title: "Refactor (Ruby)", body: refactorBody(lang) },
@@ -1,4 +1,4 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  const FRAMEWORKS = [
3
3
  ["actix-web", "actix-web"],
4
4
  ["axum", "axum"],
@@ -57,7 +57,7 @@ function rules(detection, lang) {
57
57
  }
58
58
  function promptTemplates(detection, lang) {
59
59
  const t = TEXTS[lang];
60
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
60
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
61
61
  return [
62
62
  { id: "review", title: "Code Review (Rust)", body: reviewBody(lang, t.reviewFocus, framework) },
63
63
  { id: "refactor", title: "Refactor (Rust)", body: refactorBody(lang, t.refactorExtra) },
@@ -1,4 +1,4 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, unknownRunnerLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  const FRAMEWORKS = [
3
3
  ["playframework", "play"],
4
4
  ["akka-http", "akka"],
@@ -68,8 +68,8 @@ function rules(detection, lang) {
68
68
  }
69
69
  function promptTemplates(detection, lang) {
70
70
  const t = TEXTS[lang];
71
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
72
- const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner.value : unknownRunnerLabel(lang);
71
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
72
+ const runner = detection.testRunner?.value !== "unknown" ? detection.testRunner?.value : undefined;
73
73
  return [
74
74
  { id: "review", title: "Code Review (Scala)", body: reviewBody(lang, t.reviewFocus, framework) },
75
75
  { id: "refactor", title: "Refactor (Scala)", body: refactorBody(lang) },
@@ -1,4 +1,4 @@
1
- import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, unknownFrameworkLabel, } from "../core/i18n.js";
1
+ import { refactorBody, reviewBody, runTestsConvention, summarySentence, testingBody, } from "../core/i18n.js";
2
2
  function detect(signals) {
3
3
  const source = signals.packageSwift;
4
4
  if (!source)
@@ -52,7 +52,7 @@ function rules(detection, lang) {
52
52
  }
53
53
  function promptTemplates(detection, lang) {
54
54
  const t = TEXTS[lang];
55
- const framework = detection.framework?.value !== "none" ? detection.framework.value : unknownFrameworkLabel(lang);
55
+ const framework = detection.framework?.value !== "none" ? detection.framework?.value : undefined;
56
56
  return [
57
57
  { id: "review", title: "Code Review (Swift)", body: reviewBody(lang, t.reviewFocus, framework) },
58
58
  { id: "refactor", title: "Refactor (Swift)", body: refactorBody(lang) },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-rules-init",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Generates CLAUDE.md, AGENTS.md, copilot-instructions.md and prompt files from what your repo actually is.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,11 +10,13 @@
10
10
  "agent-rules-init": "dist/bin.js"
11
11
  },
12
12
  "files": [
13
- "dist"
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
14
16
  ],
15
17
  "repository": {
16
18
  "type": "git",
17
- "url": "https://github.com/racama29/agent-rules-init.git",
19
+ "url": "git+https://github.com/racama29/agent-rules-init.git",
18
20
  "directory": "packages/cli"
19
21
  },
20
22
  "homepage": "https://github.com/racama29/agent-rules-init#readme",