csharp-code-calisthenics-reviewer 0.1.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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "local-marketplace",
3
+ "interface": {
4
+ "displayName": "Local Plugins"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "csharp-code-calisthenics-reviewer",
9
+ "source": {
10
+ "source": "local",
11
+ "path": "./plugins/csharp-code-calisthenics-reviewer"
12
+ },
13
+ "policy": {
14
+ "installation": "AVAILABLE",
15
+ "authentication": "ON_INSTALL"
16
+ },
17
+ "category": "Productivity"
18
+ }
19
+ ]
20
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leonardo de Oliveira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # csharp-code-calisthenics-reviewer
2
+
3
+ `csharp-code-calisthenics-reviewer` is an npm-distributed skill package for reviewing a single C# file or small class against a narrow set of code-calisthenics rules and suggesting a deterministic refactor plan.
4
+
5
+ ## What v1 reviews
6
+
7
+ The skill checks exactly these 5 rules:
8
+
9
+ - Only one level of indentation per method
10
+ - Avoid `else`
11
+ - Wrap primitives in value objects when it makes sense
12
+ - First-class collections
13
+ - Small methods with intention-revealing names
14
+
15
+ The skill is instruction-heavy by design. It does not perform full automatic rewrites by default, but it may include short illustrative snippets when they clarify a proposed refactor.
16
+
17
+ ## Repo layout
18
+
19
+ ```text
20
+ src/
21
+ raw-skill/
22
+ SKILL.md
23
+ references/calisthenics-checklist.md
24
+ examples/input/OrderService.cs
25
+ examples/output/review.md
26
+ codex-plugin/
27
+ .codex-plugin/plugin.json
28
+ skills/csharp-code-calisthenics-reviewer/
29
+ bin/
30
+ installer.js
31
+ scripts/
32
+ build.js
33
+ .agents/
34
+ plugins/marketplace.json
35
+ ```
36
+
37
+ `src/raw-skill` is the canonical source. `npm run build` mirrors it into the Codex plugin skill folder and synchronizes the plugin manifest version from `package.json`.
38
+
39
+ ## Install
40
+
41
+ ### npm / npx
42
+
43
+ Build first:
44
+
45
+ ```bash
46
+ npm install
47
+ npm run build
48
+ ```
49
+
50
+ Run from the published package or locally:
51
+
52
+ ```bash
53
+ npx csharp-code-calisthenics-reviewer install --target codex --scope personal
54
+ npx csharp-code-calisthenics-reviewer install --target codex --scope repo --path /path/to/repo
55
+ npx csharp-code-calisthenics-reviewer install --target dir --path /path/to/destination
56
+ npx csharp-code-calisthenics-reviewer install --target claude --path /path/to/destination
57
+ ```
58
+
59
+ Optional flags:
60
+
61
+ ```bash
62
+ --dry-run
63
+ --force
64
+ ```
65
+
66
+ ### Skillfish
67
+
68
+ Use the raw skill bundle:
69
+
70
+ ```bash
71
+ npx csharp-code-calisthenics-reviewer install --target dir --path /path/to/skills/csharp-code-calisthenics-reviewer
72
+ ```
73
+
74
+ ### Codex local plugin install
75
+
76
+ Personal install:
77
+
78
+ ```bash
79
+ npx csharp-code-calisthenics-reviewer install --target codex --scope personal
80
+ ```
81
+
82
+ This installs the plugin to `~/.codex/plugins/csharp-code-calisthenics-reviewer` and creates or updates `~/.agents/plugins/marketplace.json`.
83
+
84
+ Repo install:
85
+
86
+ ```bash
87
+ npx csharp-code-calisthenics-reviewer install --target codex --scope repo --path /path/to/repo
88
+ ```
89
+
90
+ This installs the plugin to `<repo>/plugins/csharp-code-calisthenics-reviewer` and creates or updates `<repo>/.agents/plugins/marketplace.json`.
91
+
92
+ ### Claude-compatible path copy
93
+
94
+ ```bash
95
+ npx csharp-code-calisthenics-reviewer install --target claude --path /path/to/destination
96
+ ```
97
+
98
+ This performs an explicit path-based copy only. v1 does not assume or document an official Claude install location.
99
+
100
+ ## Build and verify
101
+
102
+ ```bash
103
+ npm run build
104
+ python C:/Users/USUARIO/.codex/skills/.system/skill-creator/scripts/quick_validate.py src/raw-skill
105
+ npm pack
106
+ ```
107
+
108
+ On Windows PowerShell, if `npm pack` is blocked by `npm.ps1` execution policy or by the global npm cache, use:
109
+
110
+ ```powershell
111
+ npm run pack:ps
112
+ ```
113
+
114
+ ## Marketplace metadata
115
+
116
+ The Codex plugin manifest lives at `src/codex-plugin/.codex-plugin/plugin.json`.
117
+
118
+ The repository also includes `.agents/plugins/marketplace.json` as a repo-local marketplace catalog entry that points to `./plugins/csharp-code-calisthenics-reviewer`. Installer runs generate or update marketplace entries at the chosen destination without duplicating existing entries.
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+
7
+ const PLUGIN_NAME = "csharp-code-calisthenics-reviewer";
8
+ const DEFAULT_ENTRY = {
9
+ name: PLUGIN_NAME,
10
+ source: {
11
+ source: "local",
12
+ path: `./plugins/${PLUGIN_NAME}`
13
+ },
14
+ policy: {
15
+ installation: "AVAILABLE",
16
+ authentication: "ON_INSTALL"
17
+ },
18
+ category: "Productivity"
19
+ };
20
+
21
+ function fail(message) {
22
+ console.error(message);
23
+ process.exit(1);
24
+ }
25
+
26
+ function parseArgs(argv) {
27
+ const parsed = {
28
+ command: null,
29
+ target: null,
30
+ scope: null,
31
+ path: null,
32
+ force: false,
33
+ dryRun: false
34
+ };
35
+
36
+ const tokens = [...argv];
37
+ if (tokens.length > 0 && !tokens[0].startsWith("--")) {
38
+ parsed.command = tokens.shift();
39
+ }
40
+
41
+ for (let index = 0; index < tokens.length; index += 1) {
42
+ const token = tokens[index];
43
+ switch (token) {
44
+ case "--target":
45
+ parsed.target = tokens[++index];
46
+ break;
47
+ case "--scope":
48
+ parsed.scope = tokens[++index];
49
+ break;
50
+ case "--path":
51
+ parsed.path = tokens[++index];
52
+ break;
53
+ case "--force":
54
+ parsed.force = true;
55
+ break;
56
+ case "--dry-run":
57
+ parsed.dryRun = true;
58
+ break;
59
+ default:
60
+ fail(`Unknown argument: ${token}`);
61
+ }
62
+ }
63
+
64
+ return parsed;
65
+ }
66
+
67
+ function resolvePackageRoot() {
68
+ return path.resolve(__dirname, "..");
69
+ }
70
+
71
+ function ensureExists(targetPath, label) {
72
+ if (!fs.existsSync(targetPath)) {
73
+ fail(`${label} not found: ${targetPath}. Run "npm run build" before installing.`);
74
+ }
75
+ }
76
+
77
+ function readJson(filePath, fallback) {
78
+ if (!fs.existsSync(filePath)) {
79
+ return fallback;
80
+ }
81
+
82
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
83
+ }
84
+
85
+ function writeJson(filePath, value, dryRun) {
86
+ const payload = `${JSON.stringify(value, null, 2)}\n`;
87
+ if (dryRun) {
88
+ console.log(`[dry-run] write ${filePath}`);
89
+ return;
90
+ }
91
+
92
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
93
+ fs.writeFileSync(filePath, payload, "utf8");
94
+ }
95
+
96
+ function removeDir(targetPath, dryRun) {
97
+ if (!fs.existsSync(targetPath)) {
98
+ return;
99
+ }
100
+
101
+ if (dryRun) {
102
+ console.log(`[dry-run] remove ${targetPath}`);
103
+ return;
104
+ }
105
+
106
+ fs.rmSync(targetPath, { recursive: true, force: true });
107
+ }
108
+
109
+ function listFiles(rootPath) {
110
+ const results = [];
111
+
112
+ function visit(currentPath) {
113
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ const absolutePath = path.join(currentPath, entry.name);
116
+ if (entry.isDirectory()) {
117
+ visit(absolutePath);
118
+ } else {
119
+ results.push(path.relative(rootPath, absolutePath));
120
+ }
121
+ }
122
+ }
123
+
124
+ visit(rootPath);
125
+ return results.sort();
126
+ }
127
+
128
+ function directoriesMatch(sourcePath, destinationPath) {
129
+ if (!fs.existsSync(sourcePath) || !fs.existsSync(destinationPath)) {
130
+ return false;
131
+ }
132
+
133
+ const sourceFiles = listFiles(sourcePath);
134
+ const destinationFiles = listFiles(destinationPath);
135
+ if (sourceFiles.length !== destinationFiles.length) {
136
+ return false;
137
+ }
138
+
139
+ for (let index = 0; index < sourceFiles.length; index += 1) {
140
+ if (sourceFiles[index] !== destinationFiles[index]) {
141
+ return false;
142
+ }
143
+
144
+ const sourceContent = fs.readFileSync(path.join(sourcePath, sourceFiles[index]), "utf8");
145
+ const destinationContent = fs.readFileSync(path.join(destinationPath, destinationFiles[index]), "utf8");
146
+ if (sourceContent !== destinationContent) {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ return true;
152
+ }
153
+
154
+ function copyDir(sourcePath, destinationPath, options) {
155
+ const { dryRun } = options;
156
+ if (dryRun) {
157
+ console.log(`[dry-run] copy ${sourcePath} -> ${destinationPath}`);
158
+ return;
159
+ }
160
+
161
+ fs.mkdirSync(destinationPath, { recursive: true });
162
+ fs.cpSync(sourcePath, destinationPath, { recursive: true });
163
+ }
164
+
165
+ function copyIntoDestination(sourcePath, destinationPath, options) {
166
+ const { force, dryRun } = options;
167
+ const destinationExists = fs.existsSync(destinationPath);
168
+
169
+ if (destinationExists && !force) {
170
+ if (directoriesMatch(sourcePath, destinationPath)) {
171
+ console.log(`Destination already matches source: ${destinationPath}`);
172
+ return "unchanged";
173
+ }
174
+
175
+ fail(`Destination already exists and differs: ${destinationPath}. Re-run with --force to overwrite.`);
176
+ }
177
+
178
+ if (destinationExists) {
179
+ removeDir(destinationPath, dryRun);
180
+ }
181
+
182
+ copyDir(sourcePath, destinationPath, options);
183
+ return dryRun ? "dry-run" : "copied";
184
+ }
185
+
186
+ function ensureMarketplaceShape(marketplace) {
187
+ const next = marketplace ?? {};
188
+ if (typeof next.name !== "string") {
189
+ next.name = "local-marketplace";
190
+ }
191
+
192
+ if (!next.interface || typeof next.interface !== "object") {
193
+ next.interface = { displayName: "Local Plugins" };
194
+ }
195
+
196
+ if (typeof next.interface.displayName !== "string") {
197
+ next.interface.displayName = "Local Plugins";
198
+ }
199
+
200
+ if (!Array.isArray(next.plugins)) {
201
+ next.plugins = [];
202
+ }
203
+
204
+ return next;
205
+ }
206
+
207
+ function updateMarketplace(marketplacePath, entry, dryRun) {
208
+ const marketplace = ensureMarketplaceShape(readJson(marketplacePath, null));
209
+ const existingIndex = marketplace.plugins.findIndex((plugin) => plugin.name === entry.name);
210
+
211
+ if (existingIndex >= 0) {
212
+ marketplace.plugins[existingIndex] = entry;
213
+ } else {
214
+ marketplace.plugins.push(entry);
215
+ }
216
+
217
+ writeJson(marketplacePath, marketplace, dryRun);
218
+ }
219
+
220
+ function installCodex(scope, repoPath, options) {
221
+ const packageRoot = resolvePackageRoot();
222
+ const pluginSource = path.join(packageRoot, "src", "codex-plugin");
223
+ ensureExists(pluginSource, "Built Codex plugin");
224
+
225
+ let pluginDestination;
226
+ let marketplacePath;
227
+ let entry;
228
+
229
+ if (scope === "personal") {
230
+ const homeDir = os.homedir();
231
+ pluginDestination = path.join(homeDir, ".codex", "plugins", PLUGIN_NAME);
232
+ marketplacePath = path.join(homeDir, ".agents", "plugins", "marketplace.json");
233
+ entry = {
234
+ ...DEFAULT_ENTRY,
235
+ source: {
236
+ source: "local",
237
+ path: pluginDestination
238
+ }
239
+ };
240
+ } else if (scope === "repo") {
241
+ if (!repoPath) {
242
+ fail('Missing required "--path" for repo-scoped Codex install.');
243
+ }
244
+
245
+ const repoRoot = path.resolve(repoPath);
246
+ pluginDestination = path.join(repoRoot, "plugins", PLUGIN_NAME);
247
+ marketplacePath = path.join(repoRoot, ".agents", "plugins", "marketplace.json");
248
+ entry = { ...DEFAULT_ENTRY };
249
+ } else {
250
+ fail('Codex installs require "--scope personal" or "--scope repo".');
251
+ }
252
+
253
+ const status = copyIntoDestination(pluginSource, pluginDestination, options);
254
+ updateMarketplace(marketplacePath, entry, options.dryRun);
255
+
256
+ if (status === "dry-run") {
257
+ console.log(`Dry-run completed for Codex plugin destination ${pluginDestination}`);
258
+ } else if (status === "unchanged") {
259
+ console.log(`Codex plugin already up to date at ${pluginDestination}`);
260
+ } else {
261
+ console.log(`Installed Codex plugin to ${pluginDestination}`);
262
+ }
263
+
264
+ console.log(`${options.dryRun ? "Dry-run updated" : "Updated"} marketplace at ${marketplacePath}`);
265
+ }
266
+
267
+ function installRawSkill(targetPath, options, label) {
268
+ const packageRoot = resolvePackageRoot();
269
+ const rawSkillSource = path.join(packageRoot, "src", "raw-skill");
270
+ ensureExists(rawSkillSource, "Raw skill bundle");
271
+
272
+ if (!targetPath) {
273
+ fail('Missing required "--path" for this install target.');
274
+ }
275
+
276
+ const destination = path.resolve(targetPath);
277
+ const status = copyIntoDestination(rawSkillSource, destination, options);
278
+ if (status === "dry-run") {
279
+ console.log(`Dry-run completed for ${label} destination ${destination}`);
280
+ return;
281
+ }
282
+
283
+ if (status === "unchanged") {
284
+ console.log(`${label} bundle already up to date at ${destination}`);
285
+ return;
286
+ }
287
+
288
+ console.log(`Installed ${label} bundle to ${destination}`);
289
+ }
290
+
291
+ function printUsageAndExit() {
292
+ const lines = [
293
+ "Usage:",
294
+ " csharp-code-calisthenics-reviewer install --target codex --scope personal [--force] [--dry-run]",
295
+ " csharp-code-calisthenics-reviewer install --target codex --scope repo --path <repo-root> [--force] [--dry-run]",
296
+ " csharp-code-calisthenics-reviewer install --target dir --path <destination> [--force] [--dry-run]",
297
+ " csharp-code-calisthenics-reviewer install --target claude --path <destination> [--force] [--dry-run]"
298
+ ];
299
+
300
+ console.log(lines.join("\n"));
301
+ process.exit(1);
302
+ }
303
+
304
+ function main() {
305
+ const args = parseArgs(process.argv.slice(2));
306
+
307
+ if (args.command !== "install" || !args.target) {
308
+ printUsageAndExit();
309
+ }
310
+
311
+ const options = { force: args.force, dryRun: args.dryRun };
312
+
313
+ switch (args.target) {
314
+ case "codex":
315
+ installCodex(args.scope, args.path, options);
316
+ break;
317
+ case "dir":
318
+ installRawSkill(args.path, options, "raw skill");
319
+ break;
320
+ case "claude":
321
+ installRawSkill(args.path, options, "Claude-compatible");
322
+ break;
323
+ default:
324
+ fail(`Unsupported target: ${args.target}`);
325
+ }
326
+ }
327
+
328
+ main();
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "csharp-code-calisthenics-reviewer",
3
+ "version": "0.1.0",
4
+ "description": "Deterministic C# code calisthenics review skill packaged as a raw skill bundle and Codex plugin.",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "csharp-code-calisthenics-reviewer": "./bin/installer.js"
8
+ },
9
+ "scripts": {
10
+ "build": "node scripts/build.js",
11
+ "pack:ps": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"$env:npm_config_cache=(Get-Location).Path + '\\.tmp\\npm-cache'; npm.cmd pack\""
12
+ },
13
+ "files": [
14
+ "src/raw-skill/**",
15
+ "src/codex-plugin/**",
16
+ "scripts/build.js",
17
+ "bin/installer.js",
18
+ "README.md",
19
+ "LICENSE",
20
+ ".agents/plugins/marketplace.json"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18"
24
+ }
25
+ }
@@ -0,0 +1,110 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const PLUGIN_NAME = "csharp-code-calisthenics-reviewer";
5
+ const REQUIRED_SKILL_FILES = [
6
+ "SKILL.md",
7
+ path.join("references", "calisthenics-checklist.md"),
8
+ path.join("examples", "input", "OrderService.cs"),
9
+ path.join("examples", "output", "review.md")
10
+ ];
11
+
12
+ function fail(message) {
13
+ throw new Error(message);
14
+ }
15
+
16
+ function readJson(filePath) {
17
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
18
+ }
19
+
20
+ function writeJson(filePath, value) {
21
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
22
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
23
+ }
24
+
25
+ function listFiles(rootPath) {
26
+ const results = [];
27
+
28
+ function visit(currentPath) {
29
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ const absolutePath = path.join(currentPath, entry.name);
32
+ const relativePath = path.relative(rootPath, absolutePath);
33
+ if (entry.isDirectory()) {
34
+ visit(absolutePath);
35
+ } else {
36
+ results.push(relativePath);
37
+ }
38
+ }
39
+ }
40
+
41
+ visit(rootPath);
42
+ return results.sort();
43
+ }
44
+
45
+ function assertRequiredFiles(rootPath) {
46
+ for (const relativePath of REQUIRED_SKILL_FILES) {
47
+ const absolutePath = path.join(rootPath, relativePath);
48
+ if (!fs.existsSync(absolutePath)) {
49
+ fail(`Required skill file is missing: ${absolutePath}`);
50
+ }
51
+ }
52
+ }
53
+
54
+ function copyDir(sourcePath, destinationPath) {
55
+ fs.rmSync(destinationPath, { recursive: true, force: true });
56
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
57
+ fs.cpSync(sourcePath, destinationPath, { recursive: true });
58
+ }
59
+
60
+ function assertParity(sourcePath, destinationPath) {
61
+ const sourceFiles = listFiles(sourcePath);
62
+ const destinationFiles = listFiles(destinationPath);
63
+ const sourceJoined = sourceFiles.join("\n");
64
+ const destinationJoined = destinationFiles.join("\n");
65
+
66
+ if (sourceJoined !== destinationJoined) {
67
+ fail("Raw skill and plugin skill file lists differ after build.");
68
+ }
69
+
70
+ for (const relativePath of sourceFiles) {
71
+ const sourceContent = fs.readFileSync(path.join(sourcePath, relativePath), "utf8");
72
+ const destinationContent = fs.readFileSync(path.join(destinationPath, relativePath), "utf8");
73
+ if (sourceContent !== destinationContent) {
74
+ fail(`Skill parity mismatch after build: ${relativePath}`);
75
+ }
76
+ }
77
+ }
78
+
79
+ function syncPluginManifest(packageRoot) {
80
+ const packageJsonPath = path.join(packageRoot, "package.json");
81
+ const pluginJsonPath = path.join(packageRoot, "src", "codex-plugin", ".codex-plugin", "plugin.json");
82
+ const packageJson = readJson(packageJsonPath);
83
+ const pluginJson = readJson(pluginJsonPath);
84
+
85
+ pluginJson.name = PLUGIN_NAME;
86
+ pluginJson.version = packageJson.version;
87
+ pluginJson.skills = "./skills/";
88
+
89
+ writeJson(pluginJsonPath, pluginJson);
90
+
91
+ const reloaded = readJson(pluginJsonPath);
92
+ if (reloaded.version !== packageJson.version) {
93
+ fail("Plugin manifest version did not match package.json after synchronization.");
94
+ }
95
+ }
96
+
97
+ function main() {
98
+ const packageRoot = path.resolve(__dirname, "..");
99
+ const rawSkillRoot = path.join(packageRoot, "src", "raw-skill");
100
+ const pluginSkillRoot = path.join(packageRoot, "src", "codex-plugin", "skills", PLUGIN_NAME);
101
+
102
+ assertRequiredFiles(rawSkillRoot);
103
+ copyDir(rawSkillRoot, pluginSkillRoot);
104
+ assertParity(rawSkillRoot, pluginSkillRoot);
105
+ syncPluginManifest(packageRoot);
106
+
107
+ console.log("Build completed successfully.");
108
+ }
109
+
110
+ main();
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "csharp-code-calisthenics-reviewer",
3
+ "version": "0.1.0",
4
+ "description": "Review a C# file or small class against a fixed set of code-calisthenics rules and suggest a refactor plan.",
5
+ "skills": "./skills/"
6
+ }
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: csharp-code-calisthenics-reviewer
3
+ description: Review a single C# file or small C# class against a fixed, narrow set of code-calisthenics rules and produce a deterministic refactor plan. Use when asked to critique or review C# code for indentation depth, else usage, primitive obsession, missing first-class collections, and overly large or poorly named methods.
4
+ ---
5
+
6
+ # C# Code Calisthenics Reviewer
7
+
8
+ Review one C# file or one small class at a time. Keep the review narrow, deterministic, and grounded in the provided code.
9
+
10
+ ## Rules
11
+
12
+ Check only these 5 rules:
13
+
14
+ 1. Only one level of indentation per method
15
+ 2. Avoid `else`
16
+ 3. Wrap primitives in value objects when it makes sense
17
+ 4. First-class collections
18
+ 5. Small methods with intention-revealing names
19
+
20
+ Do not expand into the broader code-calisthenics catalog unless the user explicitly asks for that.
21
+
22
+ ## Working style
23
+
24
+ - Read the code carefully before judging it.
25
+ - Stay concrete and repeatable. Do not use random scoring, vague style commentary, or speculative claims.
26
+ - Base every finding on visible code in the provided file or class.
27
+ - Mention guard-clause opportunities when they help flatten control flow.
28
+ - Do not perform a full automatic rewrite by default.
29
+ - Include a short illustrative snippet only when it makes a recommendation easier to understand.
30
+
31
+ ## Review process
32
+
33
+ 1. Identify the class or methods under review.
34
+ 2. Evaluate each of the 5 rules in order.
35
+ 3. Record only the findings that are supported by the code.
36
+ 4. Prioritize issues that reduce nesting, split responsibilities, and clarify domain language.
37
+ 5. End with a refactor plan ordered by highest leverage first.
38
+
39
+ ## Output format
40
+
41
+ Use this structure:
42
+
43
+ ### Findings
44
+
45
+ For each rule:
46
+
47
+ - `Rule:` the exact rule name
48
+ - `Assessment:` `Pass`, `Concern`, or `Not applicable`
49
+ - `Evidence:` a brief code-specific explanation
50
+ - `Suggestion:` one concrete next step
51
+
52
+ ### Refactor plan
53
+
54
+ Provide 3 to 6 ordered steps. Each step should describe a refactor action, not just a diagnosis.
55
+
56
+ ### Optional snippet
57
+
58
+ Only include this section when a very short snippet clarifies one suggested refactor. Keep it focused on one change, not a full rewritten file.
59
+
60
+ ## References
61
+
62
+ - Read `references/calisthenics-checklist.md` for the review checklist and refactor priorities.
63
+ - Use `examples/input/OrderService.cs` and `examples/output/review.md` as the canonical example of expected scope and output shape.
@@ -0,0 +1,51 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+
5
+ namespace Example;
6
+
7
+ public class OrderService
8
+ {
9
+ public void Process(List<Order> orders, decimal discountRate, string customerType)
10
+ {
11
+ if (orders == null)
12
+ {
13
+ throw new ArgumentNullException(nameof(orders));
14
+ }
15
+ else
16
+ {
17
+ foreach (var order in orders)
18
+ {
19
+ if (order.Total > 100)
20
+ {
21
+ if (customerType == "VIP")
22
+ {
23
+ order.Total = order.Total - (order.Total * discountRate);
24
+ }
25
+ else
26
+ {
27
+ order.Total = order.Total - 5;
28
+ }
29
+ }
30
+ }
31
+
32
+ Save(orders);
33
+ Notify(customerType, orders.Count);
34
+ }
35
+ }
36
+
37
+ private void Save(List<Order> orders)
38
+ {
39
+ Console.WriteLine($"Saved {orders.Count} orders.");
40
+ }
41
+
42
+ private void Notify(string customerType, int orderCount)
43
+ {
44
+ Console.WriteLine($"Notified {customerType} customer about {orderCount} orders.");
45
+ }
46
+ }
47
+
48
+ public class Order
49
+ {
50
+ public decimal Total { get; set; }
51
+ }
@@ -0,0 +1,43 @@
1
+ # Findings
2
+
3
+ - Rule: Only one level of indentation per method
4
+ Assessment: Concern
5
+ Evidence: `Process` nests a null check, a `foreach`, an order-total condition, and a customer-type condition. The deepest branch goes well beyond one indentation level.
6
+ Suggestion: Use a guard clause for `orders == null`, then extract the discount decision into a separate method or policy object.
7
+
8
+ - Rule: Avoid `else`
9
+ Assessment: Concern
10
+ Evidence: `Process` contains an `else` after the null check and another `else` inside the discount logic.
11
+ Suggestion: Replace both branches with guard clauses or early returns so the happy path stays flat.
12
+
13
+ - Rule: Wrap primitives in value objects when it makes sense
14
+ Assessment: Concern
15
+ Evidence: `discountRate` and `customerType` carry domain meaning but are represented as raw `decimal` and `string`.
16
+ Suggestion: Introduce value objects such as `DiscountRate` and `CustomerType` to make valid states explicit.
17
+
18
+ - Rule: First-class collections
19
+ Assessment: Concern
20
+ Evidence: `List<Order>` is passed through `Process` and `Save` as a raw collection with behavior spread across the service.
21
+ Suggestion: Introduce an `OrderBatch` or similar collection abstraction to hold iteration and batch-level behavior.
22
+
23
+ - Rule: Small methods with intention-revealing names
24
+ Assessment: Concern
25
+ Evidence: `Process` validates input, applies pricing rules, persists data, and sends notifications. The name is broad and hides multiple responsibilities.
26
+ Suggestion: Split the work into smaller methods with explicit names such as `ApplyDiscounts`, `SaveBatch`, and `NotifyCustomer`.
27
+
28
+ # Refactor plan
29
+
30
+ 1. Replace the outer `if/else` with a guard clause so `Process` starts with the valid path.
31
+ 2. Extract discount selection into a small method or domain object to remove nested conditional logic.
32
+ 3. Introduce value objects for `CustomerType` and `DiscountRate` to replace raw primitives.
33
+ 4. Replace raw `List<Order>` parameters with an `OrderBatch` collection abstraction.
34
+ 5. Split `Process` into smaller methods whose names reveal validation, pricing, persistence, and notification separately.
35
+
36
+ # Optional snippet
37
+
38
+ ```csharp
39
+ if (orders is null)
40
+ {
41
+ throw new ArgumentNullException(nameof(orders));
42
+ }
43
+ ```
@@ -0,0 +1,17 @@
1
+ # Code Calisthenics Checklist
2
+
3
+ ## Core checks
4
+ - More than one indentation level inside a method?
5
+ - Any else blocks?
6
+ - Any primitive obsession?
7
+ - Any collection passed around without its own abstraction?
8
+ - Any method doing more than one thing?
9
+ - Any name that hides domain meaning?
10
+ - Any obvious guard clause missing?
11
+
12
+ ## Refactor priorities
13
+ 1. Flatten control flow
14
+ 2. Extract domain concepts
15
+ 3. Improve names
16
+ 4. Separate responsibilities
17
+ 5. Reduce incidental complexity
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: csharp-code-calisthenics-reviewer
3
+ description: Review a single C# file or small C# class against a fixed, narrow set of code-calisthenics rules and produce a deterministic refactor plan. Use when asked to critique or review C# code for indentation depth, else usage, primitive obsession, missing first-class collections, and overly large or poorly named methods.
4
+ ---
5
+
6
+ # C# Code Calisthenics Reviewer
7
+
8
+ Review one C# file or one small class at a time. Keep the review narrow, deterministic, and grounded in the provided code.
9
+
10
+ ## Rules
11
+
12
+ Check only these 5 rules:
13
+
14
+ 1. Only one level of indentation per method
15
+ 2. Avoid `else`
16
+ 3. Wrap primitives in value objects when it makes sense
17
+ 4. First-class collections
18
+ 5. Small methods with intention-revealing names
19
+
20
+ Do not expand into the broader code-calisthenics catalog unless the user explicitly asks for that.
21
+
22
+ ## Working style
23
+
24
+ - Read the code carefully before judging it.
25
+ - Stay concrete and repeatable. Do not use random scoring, vague style commentary, or speculative claims.
26
+ - Base every finding on visible code in the provided file or class.
27
+ - Mention guard-clause opportunities when they help flatten control flow.
28
+ - Do not perform a full automatic rewrite by default.
29
+ - Include a short illustrative snippet only when it makes a recommendation easier to understand.
30
+
31
+ ## Review process
32
+
33
+ 1. Identify the class or methods under review.
34
+ 2. Evaluate each of the 5 rules in order.
35
+ 3. Record only the findings that are supported by the code.
36
+ 4. Prioritize issues that reduce nesting, split responsibilities, and clarify domain language.
37
+ 5. End with a refactor plan ordered by highest leverage first.
38
+
39
+ ## Output format
40
+
41
+ Use this structure:
42
+
43
+ ### Findings
44
+
45
+ For each rule:
46
+
47
+ - `Rule:` the exact rule name
48
+ - `Assessment:` `Pass`, `Concern`, or `Not applicable`
49
+ - `Evidence:` a brief code-specific explanation
50
+ - `Suggestion:` one concrete next step
51
+
52
+ ### Refactor plan
53
+
54
+ Provide 3 to 6 ordered steps. Each step should describe a refactor action, not just a diagnosis.
55
+
56
+ ### Optional snippet
57
+
58
+ Only include this section when a very short snippet clarifies one suggested refactor. Keep it focused on one change, not a full rewritten file.
59
+
60
+ ## References
61
+
62
+ - Read `references/calisthenics-checklist.md` for the review checklist and refactor priorities.
63
+ - Use `examples/input/OrderService.cs` and `examples/output/review.md` as the canonical example of expected scope and output shape.
@@ -0,0 +1,51 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+
5
+ namespace Example;
6
+
7
+ public class OrderService
8
+ {
9
+ public void Process(List<Order> orders, decimal discountRate, string customerType)
10
+ {
11
+ if (orders == null)
12
+ {
13
+ throw new ArgumentNullException(nameof(orders));
14
+ }
15
+ else
16
+ {
17
+ foreach (var order in orders)
18
+ {
19
+ if (order.Total > 100)
20
+ {
21
+ if (customerType == "VIP")
22
+ {
23
+ order.Total = order.Total - (order.Total * discountRate);
24
+ }
25
+ else
26
+ {
27
+ order.Total = order.Total - 5;
28
+ }
29
+ }
30
+ }
31
+
32
+ Save(orders);
33
+ Notify(customerType, orders.Count);
34
+ }
35
+ }
36
+
37
+ private void Save(List<Order> orders)
38
+ {
39
+ Console.WriteLine($"Saved {orders.Count} orders.");
40
+ }
41
+
42
+ private void Notify(string customerType, int orderCount)
43
+ {
44
+ Console.WriteLine($"Notified {customerType} customer about {orderCount} orders.");
45
+ }
46
+ }
47
+
48
+ public class Order
49
+ {
50
+ public decimal Total { get; set; }
51
+ }
@@ -0,0 +1,43 @@
1
+ # Findings
2
+
3
+ - Rule: Only one level of indentation per method
4
+ Assessment: Concern
5
+ Evidence: `Process` nests a null check, a `foreach`, an order-total condition, and a customer-type condition. The deepest branch goes well beyond one indentation level.
6
+ Suggestion: Use a guard clause for `orders == null`, then extract the discount decision into a separate method or policy object.
7
+
8
+ - Rule: Avoid `else`
9
+ Assessment: Concern
10
+ Evidence: `Process` contains an `else` after the null check and another `else` inside the discount logic.
11
+ Suggestion: Replace both branches with guard clauses or early returns so the happy path stays flat.
12
+
13
+ - Rule: Wrap primitives in value objects when it makes sense
14
+ Assessment: Concern
15
+ Evidence: `discountRate` and `customerType` carry domain meaning but are represented as raw `decimal` and `string`.
16
+ Suggestion: Introduce value objects such as `DiscountRate` and `CustomerType` to make valid states explicit.
17
+
18
+ - Rule: First-class collections
19
+ Assessment: Concern
20
+ Evidence: `List<Order>` is passed through `Process` and `Save` as a raw collection with behavior spread across the service.
21
+ Suggestion: Introduce an `OrderBatch` or similar collection abstraction to hold iteration and batch-level behavior.
22
+
23
+ - Rule: Small methods with intention-revealing names
24
+ Assessment: Concern
25
+ Evidence: `Process` validates input, applies pricing rules, persists data, and sends notifications. The name is broad and hides multiple responsibilities.
26
+ Suggestion: Split the work into smaller methods with explicit names such as `ApplyDiscounts`, `SaveBatch`, and `NotifyCustomer`.
27
+
28
+ # Refactor plan
29
+
30
+ 1. Replace the outer `if/else` with a guard clause so `Process` starts with the valid path.
31
+ 2. Extract discount selection into a small method or domain object to remove nested conditional logic.
32
+ 3. Introduce value objects for `CustomerType` and `DiscountRate` to replace raw primitives.
33
+ 4. Replace raw `List<Order>` parameters with an `OrderBatch` collection abstraction.
34
+ 5. Split `Process` into smaller methods whose names reveal validation, pricing, persistence, and notification separately.
35
+
36
+ # Optional snippet
37
+
38
+ ```csharp
39
+ if (orders is null)
40
+ {
41
+ throw new ArgumentNullException(nameof(orders));
42
+ }
43
+ ```
@@ -0,0 +1,17 @@
1
+ # Code Calisthenics Checklist
2
+
3
+ ## Core checks
4
+ - More than one indentation level inside a method?
5
+ - Any else blocks?
6
+ - Any primitive obsession?
7
+ - Any collection passed around without its own abstraction?
8
+ - Any method doing more than one thing?
9
+ - Any name that hides domain meaning?
10
+ - Any obvious guard clause missing?
11
+
12
+ ## Refactor priorities
13
+ 1. Flatten control flow
14
+ 2. Extract domain concepts
15
+ 3. Improve names
16
+ 4. Separate responsibilities
17
+ 5. Reduce incidental complexity