@rexeus/agentic 0.3.1

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.
Files changed (33) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +201 -0
  3. package/assets/opencode/agents/analyst.md +358 -0
  4. package/assets/opencode/agents/architect.md +308 -0
  5. package/assets/opencode/agents/developer.md +311 -0
  6. package/assets/opencode/agents/lead.md +368 -0
  7. package/assets/opencode/agents/refiner.md +418 -0
  8. package/assets/opencode/agents/reviewer.md +285 -0
  9. package/assets/opencode/agents/scout.md +241 -0
  10. package/assets/opencode/agents/tester.md +323 -0
  11. package/assets/opencode/commands/agentic-commit.md +128 -0
  12. package/assets/opencode/commands/agentic-develop.md +170 -0
  13. package/assets/opencode/commands/agentic-plan.md +165 -0
  14. package/assets/opencode/commands/agentic-polish.md +190 -0
  15. package/assets/opencode/commands/agentic-pr.md +226 -0
  16. package/assets/opencode/commands/agentic-review.md +119 -0
  17. package/assets/opencode/commands/agentic-simplify.md +123 -0
  18. package/assets/opencode/commands/agentic-verify.md +193 -0
  19. package/bin/agentic.js +139 -0
  20. package/opencode/config.mjs +453 -0
  21. package/opencode/doctor.mjs +9 -0
  22. package/opencode/guardrails.mjs +172 -0
  23. package/opencode/install.mjs +48 -0
  24. package/opencode/manifest.mjs +34 -0
  25. package/opencode/plugin.mjs +53 -0
  26. package/opencode/uninstall.mjs +64 -0
  27. package/package.json +69 -0
  28. package/skills/conventions/SKILL.md +83 -0
  29. package/skills/git-conventions/SKILL.md +141 -0
  30. package/skills/quality-patterns/SKILL.md +73 -0
  31. package/skills/security/SKILL.md +77 -0
  32. package/skills/setup/SKILL.md +105 -0
  33. package/skills/testing/SKILL.md +113 -0
@@ -0,0 +1,172 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { execSync } from "node:child_process";
3
+
4
+ const SECRET_ASSIGNMENT_QUOTED =
5
+ /(password|passwd|secret|api_?key|access_?key|private_?key)"?[\t ]*[:=][\t ]*["'][^\s"']{4,}/i;
6
+ const SECRET_ASSIGNMENT_UNQUOTED =
7
+ /(password|passwd|secret|api_?key|access_?key|private_?key)[\t ]*=[\t ]*[^\s"'=,;}{]{4,}/i;
8
+ const TOKEN_PATTERN =
9
+ /(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36,}|gho_[a-zA-Z0-9]{36,}|github_pat_[a-zA-Z0-9_]{20,}|aws_[a-zA-Z0-9/+=]{20,}|AKIA[A-Z0-9]{16}|sk_live_[a-zA-Z0-9]{20,}|sk_test_[a-zA-Z0-9]{20,}|xoxb-[a-zA-Z0-9-]{20,}|xoxp-[a-zA-Z0-9-]{20,})/;
10
+ const COMMIT_TYPES = "feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert";
11
+ const COMMIT_HEADER_RE = new RegExp(`^(${COMMIT_TYPES})(\\([a-zA-Z0-9_./-]+\\))?!?:[\\t ].+`);
12
+
13
+ let conventionalHistoryCache;
14
+
15
+ const JS_TS_FILE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
16
+ const BINARY_OR_GENERATED_RE =
17
+ /\.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|lock|min\.js|min\.css|map)$/;
18
+
19
+ const getFirstString = (...values) => {
20
+ for (const value of values) {
21
+ if (typeof value === "string" && value.length > 0) {
22
+ return value;
23
+ }
24
+ }
25
+ return "";
26
+ };
27
+
28
+ export const extractFilePath = (args) =>
29
+ getFirstString(args?.filePath, args?.file_path, args?.path);
30
+
31
+ export const extractWriteContent = (args) =>
32
+ getFirstString(args?.content, args?.newString, args?.new_string);
33
+
34
+ export const findSecretViolations = ({ filePath, content }) => {
35
+ if (!content) {
36
+ return [];
37
+ }
38
+
39
+ const fileLabel = filePath ?? "file";
40
+ const violations = [];
41
+
42
+ if (SECRET_ASSIGNMENT_QUOTED.test(content) || SECRET_ASSIGNMENT_UNQUOTED.test(content)) {
43
+ violations.push(
44
+ `Possible hardcoded secret detected - do not write credentials to ${fileLabel}`,
45
+ );
46
+ }
47
+
48
+ if (TOKEN_PATTERN.test(content)) {
49
+ violations.push(`Possible API token detected - do not write tokens to ${fileLabel}`);
50
+ }
51
+
52
+ return violations;
53
+ };
54
+
55
+ const extractCommitMessage = (command) => {
56
+ if (!command || !/(^|&&|;|\|)[\t ]*git[\t ]+commit/.test(command)) {
57
+ return "";
58
+ }
59
+
60
+ const heredocMatch = command.match(/cat <<.*EOF([\s\S]*?)\nEOF\n?/);
61
+ if (heredocMatch && heredocMatch[1]) {
62
+ return heredocMatch[1].trim();
63
+ }
64
+
65
+ const quotedMatch = command.match(/-[a-z]*m[\t ]*["']([^"']+)["']/);
66
+ if (quotedMatch && quotedMatch[1]) {
67
+ return quotedMatch[1];
68
+ }
69
+
70
+ return "";
71
+ };
72
+
73
+ export const validateConventionalCommitCommand = (command) => {
74
+ const message = extractCommitMessage(command);
75
+
76
+ if (!message) {
77
+ return [];
78
+ }
79
+
80
+ if (!projectUsesConventionalCommits()) {
81
+ return [];
82
+ }
83
+
84
+ const header = message.split("\n")[0] ?? "";
85
+ const issues = [];
86
+
87
+ if (!new RegExp(`^(${COMMIT_TYPES})`).test(header)) {
88
+ issues.push(`First line must start with a valid type (${COMMIT_TYPES})`);
89
+ }
90
+
91
+ if (!COMMIT_HEADER_RE.test(header)) {
92
+ issues.push("Format must be: type[optional scope]: description - colon and space required");
93
+ }
94
+
95
+ const description = header.replace(/^[^:]*:[\t ]*/, "");
96
+ if (description) {
97
+ const firstChar = description[0];
98
+ if (/[A-Z]/.test(firstChar)) {
99
+ issues.push("Description should start lowercase after ': '");
100
+ }
101
+
102
+ if (description.endsWith(".")) {
103
+ issues.push("Description should not end with a period");
104
+ }
105
+ }
106
+
107
+ if (header.length > 100) {
108
+ issues.push(`First line exceeds 100 characters (found ${header.length})`);
109
+ }
110
+
111
+ return issues;
112
+ };
113
+
114
+ const projectUsesConventionalCommits = () => {
115
+ if (typeof conventionalHistoryCache === "boolean") {
116
+ return conventionalHistoryCache;
117
+ }
118
+
119
+ try {
120
+ const recent = execSync("git log --format=%s -10", {
121
+ encoding: "utf8",
122
+ stdio: ["ignore", "pipe", "ignore"],
123
+ })
124
+ .split("\n")
125
+ .map((line) => line.trim())
126
+ .filter(Boolean);
127
+
128
+ if (recent.length < 4) {
129
+ conventionalHistoryCache = true;
130
+ return conventionalHistoryCache;
131
+ }
132
+
133
+ const typeMatcher = new RegExp(`^(${COMMIT_TYPES})`);
134
+ const matching = recent.filter((line) => typeMatcher.test(line)).length;
135
+ conventionalHistoryCache = matching >= Math.floor(recent.length / 2);
136
+ return conventionalHistoryCache;
137
+ } catch {
138
+ conventionalHistoryCache = true;
139
+ return conventionalHistoryCache;
140
+ }
141
+ };
142
+
143
+ export const getConventionWarningsForFile = (filePath) => {
144
+ if (!filePath || BINARY_OR_GENERATED_RE.test(filePath)) {
145
+ return [];
146
+ }
147
+
148
+ let content;
149
+ try {
150
+ content = readFileSync(filePath, "utf8");
151
+ } catch {
152
+ return [];
153
+ }
154
+
155
+ const warnings = [];
156
+
157
+ if (/^(<<<<<<<|=======|>>>>>>>)/m.test(content)) {
158
+ warnings.push(`Merge conflict markers found in ${filePath} - resolve before committing`);
159
+ }
160
+
161
+ if (JS_TS_FILE_RE.test(filePath)) {
162
+ if (/(console\.log|console\.debug|\bdebugger\b)/.test(content)) {
163
+ warnings.push(`Debug statement found in ${filePath} - remove before committing`);
164
+ }
165
+
166
+ if (/(^|[^A-Za-z])TODO([^(A-Za-z)]|$)/.test(content)) {
167
+ warnings.push(`Unowned TODO in ${filePath} - use TODO(name) or TODO(#123)`);
168
+ }
169
+ }
170
+
171
+ return warnings;
172
+ };
@@ -0,0 +1,48 @@
1
+ import {
2
+ backupConfigFile,
3
+ ensureOpenCodeConfigDir,
4
+ ensurePluginEntry,
5
+ getDoctorStatus,
6
+ installGlobalAgents,
7
+ installGlobalCommands,
8
+ installGlobalSkills,
9
+ readOpenCodeConfig,
10
+ validateOpenCodeConfigForMutation,
11
+ writeOpenCodeConfig,
12
+ } from "./config.mjs";
13
+
14
+ export const installOpenCode = async () => {
15
+ const configDir = ensureOpenCodeConfigDir();
16
+ const { configPath, config, error } = readOpenCodeConfig(configDir);
17
+
18
+ if (error) {
19
+ throw new Error(error);
20
+ }
21
+
22
+ const mutationError = validateOpenCodeConfigForMutation(configPath, config);
23
+ if (mutationError) {
24
+ throw new Error(mutationError);
25
+ }
26
+
27
+ const backupPath = await backupConfigFile(configPath);
28
+ const updated = ensurePluginEntry(config);
29
+
30
+ await writeOpenCodeConfig(configPath, updated);
31
+ const commandsDir = await installGlobalCommands(configDir);
32
+ const agentsDir = await installGlobalAgents(configDir);
33
+ const skillsDir = await installGlobalSkills(configDir);
34
+ const status = await getDoctorStatus(configDir, updated);
35
+
36
+ return {
37
+ configPath,
38
+ backupPath,
39
+ commandsDir,
40
+ agentsDir,
41
+ skillsDir,
42
+ conflicts: {
43
+ commands: status.conflictingCommands,
44
+ agents: status.conflictingAgents,
45
+ skills: status.conflictingSkills,
46
+ },
47
+ };
48
+ };
@@ -0,0 +1,34 @@
1
+ export const COMMAND_FILES = [
2
+ "agentic-plan.md",
3
+ "agentic-develop.md",
4
+ "agentic-review.md",
5
+ "agentic-verify.md",
6
+ "agentic-simplify.md",
7
+ "agentic-polish.md",
8
+ "agentic-commit.md",
9
+ "agentic-pr.md",
10
+ ];
11
+
12
+ export const AGENT_FILES = [
13
+ "lead.md",
14
+ "scout.md",
15
+ "analyst.md",
16
+ "architect.md",
17
+ "developer.md",
18
+ "reviewer.md",
19
+ "tester.md",
20
+ "refiner.md",
21
+ ];
22
+
23
+ export const SKILL_NAMES = [
24
+ "conventions",
25
+ "git-conventions",
26
+ "quality-patterns",
27
+ "security",
28
+ "setup",
29
+ "testing",
30
+ ];
31
+
32
+ export const COMMAND_PREFIX = "agentic-";
33
+ export const PLUGIN_NAME = "@rexeus/agentic";
34
+ export const LOCAL_PLUGIN_PREFIX = "agentic";
@@ -0,0 +1,53 @@
1
+ import {
2
+ extractFilePath,
3
+ extractWriteContent,
4
+ findSecretViolations,
5
+ getConventionWarningsForFile,
6
+ validateConventionalCommitCommand,
7
+ } from "./guardrails.mjs";
8
+
9
+ const AgenticPlugin = async () => ({
10
+ name: "agentic",
11
+
12
+ "tool.execute.before": async (input, output) => {
13
+ if (input.tool === "write" || input.tool === "edit") {
14
+ const filePath = extractFilePath(output.args);
15
+ const content = extractWriteContent(output.args);
16
+ const violations = findSecretViolations({ filePath, content });
17
+ if (violations.length > 0) {
18
+ throw new Error(violations.join("\n"));
19
+ }
20
+ return;
21
+ }
22
+
23
+ if (input.tool === "bash") {
24
+ const command = output?.args?.command;
25
+ if (typeof command !== "string" || command.length === 0) {
26
+ return;
27
+ }
28
+
29
+ const issues = validateConventionalCommitCommand(command);
30
+ if (issues.length > 0) {
31
+ throw new Error(`Commit message validation failed:\n${issues.join("\n")}`);
32
+ }
33
+ }
34
+ },
35
+
36
+ "tool.execute.after": async (input, output) => {
37
+ if (input.tool !== "write" && input.tool !== "edit") {
38
+ return;
39
+ }
40
+
41
+ const filePath = extractFilePath(input.args);
42
+ const warnings = getConventionWarningsForFile(filePath);
43
+
44
+ if (warnings.length === 0) {
45
+ return;
46
+ }
47
+
48
+ const warningText = `[agentic warnings]\n${warnings.map((warning) => `- ${warning}`).join("\n")}`;
49
+ output.output = output.output ? `${output.output}\n\n${warningText}` : warningText;
50
+ },
51
+ });
52
+
53
+ export default AgenticPlugin;
@@ -0,0 +1,64 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ import {
4
+ backupConfigFile,
5
+ getDoctorStatus,
6
+ getOpenCodeConfigDir,
7
+ readOpenCodeConfig,
8
+ removePluginEntry,
9
+ uninstallGlobalAgents,
10
+ uninstallGlobalCommands,
11
+ uninstallGlobalSkills,
12
+ validateOpenCodeConfigForMutation,
13
+ writeOpenCodeConfig,
14
+ } from "./config.mjs";
15
+
16
+ export const uninstallOpenCode = async () => {
17
+ const configDir = getOpenCodeConfigDir();
18
+ const { configPath, config, error } = readOpenCodeConfig(configDir);
19
+
20
+ if (error) {
21
+ throw new Error(error);
22
+ }
23
+
24
+ const mutationError = validateOpenCodeConfigForMutation(configPath, config);
25
+ if (mutationError) {
26
+ throw new Error(mutationError);
27
+ }
28
+
29
+ const installStatus = await getDoctorStatus(configDir, config);
30
+ const hasManagedAssets =
31
+ installStatus.commands.installedFiles.length > 0 ||
32
+ installStatus.agents.installedFiles.length > 0 ||
33
+ installStatus.skills.installedFiles.length > 0;
34
+
35
+ if (!installStatus.pluginSources.config && !hasManagedAssets) {
36
+ return {
37
+ configPath,
38
+ backupPath: null,
39
+ removedCommands: 0,
40
+ removedAgents: 0,
41
+ removedSkills: 0,
42
+ };
43
+ }
44
+
45
+ const updated = removePluginEntry(config);
46
+ const hasConfigFile = existsSync(configPath);
47
+
48
+ const backupPath = hasConfigFile ? await backupConfigFile(configPath) : null;
49
+
50
+ if (hasConfigFile) {
51
+ await writeOpenCodeConfig(configPath, updated);
52
+ }
53
+ const removedCommands = await uninstallGlobalCommands(configDir);
54
+ const removedAgents = await uninstallGlobalAgents(configDir);
55
+ const removedSkills = await uninstallGlobalSkills(configDir);
56
+
57
+ return {
58
+ configPath,
59
+ backupPath,
60
+ removedCommands,
61
+ removedAgents,
62
+ removedSkills,
63
+ };
64
+ };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@rexeus/agentic",
3
+ "version": "0.3.1",
4
+ "description": "Multi-agent development toolkit for Claude Code and OpenCode. Specialized agents, conventions, review workflows, and quality guardrails for TypeScript and JavaScript projects.",
5
+ "keywords": [
6
+ "agentic-coding",
7
+ "claude-code",
8
+ "code-quality",
9
+ "code-review",
10
+ "conventions",
11
+ "opencode",
12
+ "plugin",
13
+ "refactoring"
14
+ ],
15
+ "homepage": "https://github.com/rexeus/agentic#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/rexeus/agentic/issues"
18
+ },
19
+ "license": "Apache-2.0",
20
+ "author": "Dennis Wentzien <dw@rexeus.com>",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/rexeus/agentic.git"
24
+ },
25
+ "bin": {
26
+ "agentic": "./bin/agentic.js"
27
+ },
28
+ "files": [
29
+ "assets/opencode/agents",
30
+ "assets/opencode/commands",
31
+ "bin",
32
+ "opencode",
33
+ "skills",
34
+ "README.md",
35
+ "LICENSE*"
36
+ ],
37
+ "type": "module",
38
+ "main": "./opencode/plugin.mjs",
39
+ "exports": {
40
+ ".": "./opencode/plugin.mjs"
41
+ },
42
+ "scripts": {
43
+ "test": "bash tests/run.sh",
44
+ "format": "oxfmt",
45
+ "sync:opencode-agents": "node scripts/sync-opencode-agents.mjs",
46
+ "sync:opencode-commands": "node scripts/sync-opencode-commands.mjs",
47
+ "check:agent-parity": "node scripts/check-agent-parity.mjs",
48
+ "check:command-parity": "node scripts/check-command-parity.mjs",
49
+ "check:opencode": "bash tests/run.sh opencode-cli",
50
+ "sync:versions": "node scripts/sync-versions.mjs",
51
+ "check:versions": "node scripts/sync-versions.mjs --check",
52
+ "release": "changeset",
53
+ "version-packages": "changeset version && pnpm run sync:versions",
54
+ "ci:publish": "pnpm publish --access public"
55
+ },
56
+ "dependencies": {
57
+ "jsonc-parser": "^3.3.1"
58
+ },
59
+ "devDependencies": {
60
+ "@changesets/cli": "2.29.6",
61
+ "oxfmt": "^0.36.0"
62
+ },
63
+ "packageManager": "pnpm@10.19.0",
64
+ "pnpm": {
65
+ "patchedDependencies": {
66
+ "@changesets/assemble-release-plan@6.0.9": "patches/@changesets__assemble-release-plan@6.0.9.patch"
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: conventions
3
+ description: Code conventions and style convictions. Applied when writing, reviewing, or analyzing code. These are non-negotiable standards, not preferences.
4
+ user-invokable: false
5
+ ---
6
+
7
+ # Code Conventions
8
+
9
+ These conventions target **TypeScript and JavaScript** projects. For other
10
+ languages, adapt the principles to idiomatic equivalents.
11
+
12
+ These are convictions, not suggestions. Apply them to all code you write or review.
13
+
14
+ ## Certainty Over Ambiguity
15
+
16
+ - Use `??` over `||`. Nullish is not falsy. Mean what you say.
17
+ - Use strict equality (`===`). Loose comparisons hide bugs.
18
+ - Discriminated unions over optional fields when states are mutually exclusive.
19
+
20
+ ## Immutability Is Clarity
21
+
22
+ - `const` by default. `let` only when mutation is required and justified.
23
+ - Mark properties `readonly` unless mutation is the explicit intent.
24
+ - Mutable state is opt-in, never opt-out.
25
+
26
+ ## Types, Not Interfaces
27
+
28
+ - Use `type` for composition, algebraic types, and everything internal.
29
+ - Use `interface` only for contracts with external consumers.
30
+ - If in doubt, use `type`.
31
+
32
+ ## Say It With Access Modifiers
33
+
34
+ - `public`, `private`, `protected` on every method and constructor parameter.
35
+ - No ambiguity. No guessing. Declare intent explicitly.
36
+
37
+ ## Naming Is Design
38
+
39
+ - Variables and functions: camelCase. Descriptive. `getUserById` not `getUser`.
40
+ - Types and classes: PascalCase. Nouns for data, adjectives for capabilities.
41
+ - Constants: SCREAMING_SNAKE_CASE only for true compile-time constants.
42
+ - Booleans: prefix with `is`, `has`, `can`, `should`. Never ambiguous.
43
+ - Files: match the primary export. `UserService.ts` exports `UserService`.
44
+ - If you can't name it clearly, you don't understand it yet.
45
+
46
+ ## Small Functions, Clear Purpose
47
+
48
+ - Every function does one thing. If you need "and" to describe it, split it.
49
+ - Aim for 20 lines or fewer. If significantly longer, consider extracting.
50
+ - Aim for 300 lines or fewer per file. If significantly longer, consider decomposing.
51
+ - Maximum nesting: 3 levels. Early returns over deep nesting.
52
+
53
+ ## Errors Are First-Class Citizens
54
+
55
+ - Never swallow errors. Every `catch` must log, rethrow, or transform.
56
+ - Typed errors with meaningful messages. `new AuthError("Token expired")` not `new Error("error")`.
57
+ - Handle errors at the appropriate boundary. Don't catch what you can't handle.
58
+ - Prefer `Result<T, E>` patterns where the language supports it.
59
+ - The unhappy path deserves as much care as the happy path.
60
+
61
+ ## Imports & Dependencies
62
+
63
+ - Named exports only. Default exports create ambiguity.
64
+ - Import order: stdlib, external packages, internal packages, relative imports.
65
+ - Every dependency is a decision. Lightweight, well-maintained, truly needed — or don't add it.
66
+
67
+ ## Comments Are a Last Resort
68
+
69
+ - If the code needs a comment to be understood, the code isn't done yet.
70
+ - Comments explain _why_, never _what_. Only when the reasoning is non-obvious.
71
+ - TODO comments include an owner or issue link. Orphan TODOs are technical debt.
72
+ - JSDoc only for public API surfaces.
73
+
74
+ ## Adapting to the Project
75
+
76
+ These conventions are the baseline. The project's voice takes precedence:
77
+
78
+ 1. Check for CLAUDE.md files in the repository
79
+ 2. Examine recent commits for naming and style patterns
80
+ 3. Look at existing tests for testing conventions
81
+ 4. If the project has its own standards, those override these defaults
82
+
83
+ Your code should look like it was always there.
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: git-conventions
3
+ description: Provides git workflow conventions including Conventional Commits, branch naming, and PR descriptions. Applied when committing, branching, or creating pull requests.
4
+ user-invokable: false
5
+ ---
6
+
7
+ # Git Conventions
8
+
9
+ Commits tell a story. Reading `git log --oneline` should explain the
10
+ evolution of a project — every change purposeful, every message clear.
11
+ When this skill is active, apply these conventions to all git operations.
12
+
13
+ ## Conventional Commits
14
+
15
+ Follow the Conventional Commits 1.0.0 specification (conventionalcommits.org):
16
+
17
+ ```
18
+ <type>[optional scope][optional !]: <description>
19
+
20
+ [optional body]
21
+
22
+ [optional footer(s)]
23
+ ```
24
+
25
+ ### Types
26
+
27
+ | Type | When to use | Semver impact |
28
+ | ---------- | ------------------------------- | ------------- |
29
+ | `feat` | New feature for the user | MINOR |
30
+ | `fix` | Bug fix | PATCH |
31
+ | `docs` | Documentation only | — |
32
+ | `style` | Formatting, no logic change | — |
33
+ | `refactor` | Code change, no feature or fix | — |
34
+ | `perf` | Performance improvement | PATCH |
35
+ | `test` | Adding or correcting tests | — |
36
+ | `build` | Build system or dependencies | — |
37
+ | `ci` | CI/CD configuration | — |
38
+ | `chore` | Maintenance, no production code | — |
39
+ | `revert` | Reverts a previous commit | — |
40
+
41
+ ### Rules
42
+
43
+ - **Description**: imperative mood, lowercase, no period. Max 100 characters.
44
+ Good: `feat(auth): add token refresh endpoint`
45
+ Bad: `feat(auth): Added Token Refresh Endpoint.`
46
+ - **Scope**: optional. Use the module or component name.
47
+ `feat(auth)`, `fix(api)`, `refactor(db)`
48
+ - **Body**: explain WHAT changed and WHY. The diff shows HOW.
49
+ Wrap at 72 characters. Separate from description with a blank line.
50
+ Multiple paragraphs are allowed — separate them with blank lines.
51
+ - **Breaking changes**: add `!` after type/scope OR `BREAKING CHANGE:` in footer.
52
+ `BREAKING-CHANGE` (hyphen) is a valid synonym.
53
+ `feat(api)!: change authentication to OAuth2`
54
+
55
+ ### Choosing the Right Type
56
+
57
+ - Changed behavior for the user? → `feat` or `fix`
58
+ - Changed internals without affecting behavior? → `refactor`
59
+ - Only moved or renamed things? → `refactor`
60
+ - Only changed comments or docs? → `docs`
61
+ - Only changed tests? → `test`
62
+ - Undoing a previous commit? → `revert`
63
+ - Multiple types in one commit? → The commit is too large. Split it.
64
+
65
+ ### Footers
66
+
67
+ Footers follow `git-trailer` format: `Token: value` or `Token #value`.
68
+ They appear after the body, separated by a blank line. One footer per line.
69
+ Multi-word tokens use `-` (exception: `BREAKING CHANGE` with space).
70
+
71
+ | Footer | Purpose | Example |
72
+ | ----------------- | ----------------- | --------------------------------------- |
73
+ | `BREAKING CHANGE` | Describe breakage | `BREAKING CHANGE: removed v1 endpoints` |
74
+ | `Fixes` | Close an issue | `Fixes #42` |
75
+ | `Refs` | Reference issues | `Refs #123, #456` |
76
+ | `Co-authored-by` | Credit co-author | `Co-authored-by: Name <email>` |
77
+ | `Reviewed-by` | Credit reviewer | `Reviewed-by: Name <email>` |
78
+
79
+ ### Examples
80
+
81
+ Minimal — title only:
82
+
83
+ ```
84
+ fix(auth): prevent token expiry race condition
85
+ ```
86
+
87
+ With body and issue reference:
88
+
89
+ ```
90
+ feat(api): add batch processing endpoint
91
+
92
+ Process up to 100 items in a single request. Uses chunked
93
+ transfer encoding for large payloads.
94
+
95
+ Fixes #234
96
+ ```
97
+
98
+ Breaking change with footer:
99
+
100
+ ```
101
+ refactor(db)!: migrate from MySQL to PostgreSQL
102
+
103
+ Replace all MySQL-specific queries with PostgreSQL equivalents.
104
+ Connection pooling now uses pgBouncer.
105
+
106
+ BREAKING CHANGE: database connection string format changed,
107
+ requires migration script before deployment
108
+ Refs #89
109
+ ```
110
+
111
+ ## Branch Naming
112
+
113
+ Pattern: `<type>/<short-description>`
114
+
115
+ ```
116
+ feat/token-refresh
117
+ fix/null-reference-login
118
+ refactor/extract-auth-service
119
+ ```
120
+
121
+ - Use lowercase and hyphens
122
+ - Keep it short but descriptive
123
+ - Match the type to Conventional Commits types
124
+
125
+ ## Commit Discipline
126
+
127
+ - **One logical change per commit.** If you need "and" to describe it, split it.
128
+ - **Never commit generated files** (dist/, build/, node_modules/).
129
+ - **Never commit secrets** (.env, credentials, API keys).
130
+ - **Never commit debug code** (console.log, debugger, TODO: remove).
131
+ - **Commit messages tell a story.** Reading `git log --oneline` should
132
+ explain the evolution of the project.
133
+
134
+ ## Adapting to the Project
135
+
136
+ Before applying these conventions:
137
+
138
+ 1. Read the last 20 commit messages: `git log --oneline -20`
139
+ 2. Check for a CONTRIBUTING.md or commit message guidelines
140
+ 3. If the project uses a different convention, follow theirs
141
+ 4. Match the language of existing commits (English, German, etc.)