@sha3/code-standards 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.
Files changed (59) hide show
  1. package/AGENTS.md +19 -0
  2. package/README.md +368 -0
  3. package/ai/adapters/codex.md +5 -0
  4. package/ai/adapters/copilot.md +5 -0
  5. package/ai/adapters/cursor.md +5 -0
  6. package/ai/adapters/windsurf.md +6 -0
  7. package/ai/constitution.md +9 -0
  8. package/bin/code-standards.mjs +1010 -0
  9. package/eslint/base.mjs +36 -0
  10. package/eslint/node.mjs +14 -0
  11. package/eslint/test.mjs +19 -0
  12. package/index.mjs +19 -0
  13. package/package.json +64 -0
  14. package/prettier/index.cjs +10 -0
  15. package/profiles/default.profile.json +38 -0
  16. package/profiles/schema.json +159 -0
  17. package/resources/ai/AGENTS.md +16 -0
  18. package/resources/ai/adapters/codex.md +5 -0
  19. package/resources/ai/adapters/copilot.md +5 -0
  20. package/resources/ai/adapters/cursor.md +5 -0
  21. package/resources/ai/adapters/windsurf.md +5 -0
  22. package/resources/ai/templates/adapters/codex.template.md +6 -0
  23. package/resources/ai/templates/adapters/copilot.template.md +6 -0
  24. package/resources/ai/templates/adapters/cursor.template.md +6 -0
  25. package/resources/ai/templates/adapters/windsurf.template.md +6 -0
  26. package/resources/ai/templates/agents.project.template.md +33 -0
  27. package/resources/ai/templates/rules/architecture.md +26 -0
  28. package/resources/ai/templates/rules/async.md +23 -0
  29. package/resources/ai/templates/rules/class-first.md +96 -0
  30. package/resources/ai/templates/rules/control-flow.md +19 -0
  31. package/resources/ai/templates/rules/errors.md +22 -0
  32. package/resources/ai/templates/rules/functions.md +27 -0
  33. package/resources/ai/templates/rules/readme.md +25 -0
  34. package/resources/ai/templates/rules/returns.md +33 -0
  35. package/resources/ai/templates/rules/testing.md +26 -0
  36. package/standards/architecture.md +24 -0
  37. package/standards/changelog-policy.md +12 -0
  38. package/standards/manifest.json +47 -0
  39. package/standards/readme.md +25 -0
  40. package/standards/schema.json +122 -0
  41. package/standards/style.md +48 -0
  42. package/standards/testing.md +18 -0
  43. package/standards/tooling.md +32 -0
  44. package/templates/node-lib/eslint.config.mjs +4 -0
  45. package/templates/node-lib/package.json +30 -0
  46. package/templates/node-lib/prettier.config.cjs +9 -0
  47. package/templates/node-lib/src/index.ts +3 -0
  48. package/templates/node-lib/test/smoke.test.ts +8 -0
  49. package/templates/node-lib/tsconfig.build.json +7 -0
  50. package/templates/node-lib/tsconfig.json +7 -0
  51. package/templates/node-service/eslint.config.mjs +4 -0
  52. package/templates/node-service/package.json +25 -0
  53. package/templates/node-service/prettier.config.cjs +9 -0
  54. package/templates/node-service/src/index.ts +17 -0
  55. package/templates/node-service/test/smoke.test.ts +37 -0
  56. package/templates/node-service/tsconfig.json +7 -0
  57. package/tsconfig/base.json +16 -0
  58. package/tsconfig/node-lib.json +12 -0
  59. package/tsconfig/node-service.json +10 -0
@@ -0,0 +1,36 @@
1
+ import js from "@eslint/js";
2
+ import tseslint from "typescript-eslint";
3
+
4
+ export default tseslint.config(
5
+ {
6
+ ignores: ["**/node_modules/**", "**/dist/**", "**/coverage/**", "**/.git/**"]
7
+ },
8
+ js.configs.recommended,
9
+ ...tseslint.configs.recommended,
10
+ {
11
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,tsx}"],
12
+ rules: {
13
+ curly: ["error", "all"]
14
+ }
15
+ },
16
+ {
17
+ files: ["**/*.{ts,mts,cts,tsx}"],
18
+ rules: {
19
+ "@typescript-eslint/consistent-type-imports": "error",
20
+ "@typescript-eslint/no-explicit-any": "error",
21
+ "@typescript-eslint/no-unused-vars": [
22
+ "error",
23
+ {
24
+ argsIgnorePattern: "^_",
25
+ varsIgnorePattern: "^_"
26
+ }
27
+ ]
28
+ }
29
+ },
30
+ {
31
+ files: ["**/*.cjs"],
32
+ rules: {
33
+ "@typescript-eslint/no-require-imports": "off"
34
+ }
35
+ }
36
+ );
@@ -0,0 +1,14 @@
1
+ import globals from "globals";
2
+ import baseConfig from "./base.mjs";
3
+
4
+ export default [
5
+ ...baseConfig,
6
+ {
7
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,tsx}"],
8
+ languageOptions: {
9
+ globals: {
10
+ ...globals.node
11
+ }
12
+ }
13
+ }
14
+ ];
@@ -0,0 +1,19 @@
1
+ import globals from "globals";
2
+ import nodeConfig from "./node.mjs";
3
+
4
+ export default [
5
+ ...nodeConfig,
6
+ {
7
+ files: [
8
+ "**/*.test.{js,mjs,cjs,ts,mts,cts}",
9
+ "**/*.spec.{js,mjs,cjs,ts,mts,cts}",
10
+ "test/**/*.{js,mjs,cjs,ts,mts,cts}"
11
+ ],
12
+ languageOptions: {
13
+ globals: {
14
+ ...globals.mocha,
15
+ ...globals.node
16
+ }
17
+ }
18
+ }
19
+ ];
package/index.mjs ADDED
@@ -0,0 +1,19 @@
1
+ import path from "node:path";
2
+ import { createRequire } from "node:module";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import eslintBase from "./eslint/base.mjs";
6
+ import eslintNode from "./eslint/node.mjs";
7
+ import eslintTest from "./eslint/test.mjs";
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const prettierConfig = require("./prettier/index.cjs");
11
+ const rootDir = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ export { eslintBase, eslintNode, eslintTest, prettierConfig };
14
+
15
+ export const tsconfigPaths = {
16
+ base: path.join(rootDir, "tsconfig", "base.json"),
17
+ nodeLib: path.join(rootDir, "tsconfig", "node-lib.json"),
18
+ nodeService: path.join(rootDir, "tsconfig", "node-service.json")
19
+ };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@sha3/code-standards",
3
+ "version": "0.1.0",
4
+ "description": "AI-first code standards, tooling exports, and project initializer",
5
+ "type": "module",
6
+ "bin": {
7
+ "code-standards": "./bin/code-standards.mjs"
8
+ },
9
+ "exports": {
10
+ ".": "./index.mjs",
11
+ "./eslint/base": "./eslint/base.mjs",
12
+ "./eslint/node": "./eslint/node.mjs",
13
+ "./eslint/test": "./eslint/test.mjs",
14
+ "./prettier": "./prettier/index.cjs",
15
+ "./tsconfig/base.json": "./tsconfig/base.json",
16
+ "./tsconfig/node-lib.json": "./tsconfig/node-lib.json",
17
+ "./tsconfig/node-service.json": "./tsconfig/node-service.json"
18
+ },
19
+ "files": [
20
+ "AGENTS.md",
21
+ "README.md",
22
+ "ai",
23
+ "bin",
24
+ "eslint",
25
+ "index.mjs",
26
+ "prettier",
27
+ "resources",
28
+ "standards",
29
+ "templates",
30
+ "tsconfig",
31
+ "profiles"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.11.0"
38
+ },
39
+ "scripts": {
40
+ "check": "npm run standards:validate && npm run profile:validate && npm run lint && npm run format:check && npm run typecheck && npm run test",
41
+ "fix": "npm run lint:fix && npm run format:write",
42
+ "standards:validate": "node scripts/validate-standards.mjs",
43
+ "lint": "eslint .",
44
+ "lint:fix": "eslint . --fix",
45
+ "format:check": "prettier --check .",
46
+ "format:write": "prettier --write .",
47
+ "typecheck": "tsc --noEmit -p tsconfig.json",
48
+ "test": "node scripts/smoke-fixtures.mjs && node --test ./test/*.test.mjs",
49
+ "release:check": "npm run check",
50
+ "release:publish": "node scripts/release-publish.mjs",
51
+ "hooks:install": "git config core.hooksPath .githooks",
52
+ "profile:validate": "node scripts/validate-profile.mjs"
53
+ },
54
+ "devDependencies": {
55
+ "@eslint/js": "^9.20.0",
56
+ "@types/node": "^22.13.10",
57
+ "ajv": "^8.17.1",
58
+ "eslint": "^9.20.1",
59
+ "globals": "^15.15.0",
60
+ "prettier": "^3.5.3",
61
+ "typescript": "^5.8.2",
62
+ "typescript-eslint": "^8.24.1"
63
+ }
64
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ printWidth: 100,
3
+ tabWidth: 2,
4
+ useTabs: false,
5
+ semi: true,
6
+ singleQuote: false,
7
+ trailingComma: "none",
8
+ bracketSpacing: true,
9
+ arrowParens: "always"
10
+ };
@@ -0,0 +1,38 @@
1
+ {
2
+ "version": "v1",
3
+ "paradigm": "class-first",
4
+ "function_size_policy": "max_30_lines_soft",
5
+ "return_policy": "single_return_strict_no_exceptions",
6
+ "class_design": "constructor_injection",
7
+ "comments_policy": "extensive",
8
+ "testing_policy": "tests_required_for_behavior_change",
9
+ "architecture": "feature_folders",
10
+ "error_handling": "exceptions_with_typed_errors",
11
+ "async_style": "async_await_only",
12
+ "class_file_policy": "one_public_class_per_file",
13
+ "type_contract_policy": "prefer_types_over_interfaces",
14
+ "mutability": "immutability_preferred",
15
+ "comment_section_blocks": [
16
+ "imports:externals",
17
+ "imports:internals",
18
+ "consts",
19
+ "types",
20
+ "private:attributes",
21
+ "private:properties",
22
+ "public:properties",
23
+ "constructor",
24
+ "static:properties",
25
+ "factory",
26
+ "private:methods",
27
+ "public:methods",
28
+ "static:methods"
29
+ ],
30
+ "comment_section_format": "jsdoc-section-tag",
31
+ "comment_sections_required_when_empty": true,
32
+ "if_requires_braces": true,
33
+ "readme_style": "top-tier",
34
+ "rule_severity_model": "all_blocking",
35
+ "language": "english_technical",
36
+ "examples_density": "rule_with_good_bad_examples",
37
+ "instruction_file_location": "root_agents_md"
38
+ }
@@ -0,0 +1,159 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sha3.dev/code-standards/profile.schema.json",
4
+ "title": "AI Style Profile",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "version",
9
+ "paradigm",
10
+ "function_size_policy",
11
+ "return_policy",
12
+ "class_design",
13
+ "comments_policy",
14
+ "testing_policy",
15
+ "architecture",
16
+ "error_handling",
17
+ "async_style",
18
+ "class_file_policy",
19
+ "type_contract_policy",
20
+ "mutability",
21
+ "comment_section_blocks",
22
+ "comment_section_format",
23
+ "comment_sections_required_when_empty",
24
+ "if_requires_braces",
25
+ "readme_style",
26
+ "rule_severity_model",
27
+ "language",
28
+ "examples_density",
29
+ "instruction_file_location"
30
+ ],
31
+ "properties": {
32
+ "version": {
33
+ "type": "string",
34
+ "const": "v1"
35
+ },
36
+ "paradigm": {
37
+ "type": "string",
38
+ "enum": ["class-first", "functional-first", "hybrid"]
39
+ },
40
+ "function_size_policy": {
41
+ "type": "string",
42
+ "enum": ["max_20_lines_hard", "max_30_lines_soft", "no_fixed_limit"]
43
+ },
44
+ "return_policy": {
45
+ "type": "string",
46
+ "enum": [
47
+ "single_return_strict_no_exceptions",
48
+ "single_return_with_guard_clauses",
49
+ "free_return_style"
50
+ ]
51
+ },
52
+ "class_design": {
53
+ "type": "string",
54
+ "enum": ["constructor_injection", "internal_instantiation", "mixed"]
55
+ },
56
+ "comments_policy": {
57
+ "type": "string",
58
+ "enum": ["extensive", "complex_logic_only", "minimal"]
59
+ },
60
+ "testing_policy": {
61
+ "type": "string",
62
+ "enum": ["tests_required_for_behavior_change", "tests_critical_only", "tests_optional"]
63
+ },
64
+ "architecture": {
65
+ "type": "string",
66
+ "enum": ["feature_folders", "layered", "simple_src_lib"]
67
+ },
68
+ "error_handling": {
69
+ "type": "string",
70
+ "enum": ["exceptions_with_typed_errors", "result_either", "mixed"]
71
+ },
72
+ "async_style": {
73
+ "type": "string",
74
+ "enum": ["async_await_only", "promise_chains", "both"]
75
+ },
76
+ "class_file_policy": {
77
+ "type": "string",
78
+ "enum": ["one_public_class_per_file", "multiple_classes_allowed", "no_rule"]
79
+ },
80
+ "type_contract_policy": {
81
+ "type": "string",
82
+ "enum": ["prefer_types_over_interfaces", "interfaces_everywhere", "interfaces_public_only"]
83
+ },
84
+ "mutability": {
85
+ "type": "string",
86
+ "enum": ["immutability_preferred", "immutability_strict", "mutable_pragmatic"]
87
+ },
88
+ "comment_section_blocks": {
89
+ "type": "array",
90
+ "minItems": 13,
91
+ "maxItems": 13,
92
+ "items": {
93
+ "type": "string",
94
+ "enum": [
95
+ "imports:externals",
96
+ "imports:internals",
97
+ "consts",
98
+ "types",
99
+ "private:attributes",
100
+ "private:properties",
101
+ "public:properties",
102
+ "constructor",
103
+ "static:properties",
104
+ "factory",
105
+ "private:methods",
106
+ "public:methods",
107
+ "static:methods"
108
+ ]
109
+ },
110
+ "const": [
111
+ "imports:externals",
112
+ "imports:internals",
113
+ "consts",
114
+ "types",
115
+ "private:attributes",
116
+ "private:properties",
117
+ "public:properties",
118
+ "constructor",
119
+ "static:properties",
120
+ "factory",
121
+ "private:methods",
122
+ "public:methods",
123
+ "static:methods"
124
+ ]
125
+ },
126
+ "comment_section_format": {
127
+ "type": "string",
128
+ "const": "jsdoc-section-tag"
129
+ },
130
+ "comment_sections_required_when_empty": {
131
+ "type": "boolean",
132
+ "const": true
133
+ },
134
+ "if_requires_braces": {
135
+ "type": "boolean",
136
+ "const": true
137
+ },
138
+ "readme_style": {
139
+ "type": "string",
140
+ "const": "top-tier"
141
+ },
142
+ "rule_severity_model": {
143
+ "type": "string",
144
+ "enum": ["all_blocking", "tiered", "all_preferred"]
145
+ },
146
+ "language": {
147
+ "type": "string",
148
+ "enum": ["english_technical", "spanish_technical", "bilingual"]
149
+ },
150
+ "examples_density": {
151
+ "type": "string",
152
+ "enum": ["rule_with_good_bad_examples", "rules_only", "rules_plus_long_templates"]
153
+ },
154
+ "instruction_file_location": {
155
+ "type": "string",
156
+ "enum": ["root_agents_md", "ai_instructions_md", "both"]
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,16 @@
1
+ # AI Template System Entry Point
2
+
3
+ This directory stores profile-aware templates used by `code-standards init`.
4
+
5
+ Template resolution order:
6
+
7
+ 1. `resources/ai/templates/agents.project.template.md`
8
+ 2. `resources/ai/templates/rules/*.md`
9
+ 3. `resources/ai/templates/adapters/*.template.md`
10
+
11
+ Rendering rules:
12
+
13
+ - Render from validated profile data (`profiles/schema.json`).
14
+ - Use deterministic token replacement.
15
+ - Preserve stable section order in generated `AGENTS.md`.
16
+ - Keep assistant adapters aligned with the generated project contract.
@@ -0,0 +1,5 @@
1
+ # Codex Adapter
2
+
3
+ - Implement directly and keep changes minimal.
4
+ - Prefer deterministic commands and explicit validation.
5
+ - Use `npm run check` as the completion gate.
@@ -0,0 +1,5 @@
1
+ # Copilot Adapter
2
+
3
+ - Generate ESM-first TypeScript code.
4
+ - Prefer small composable functions.
5
+ - Add or update tests for behavioral changes.
@@ -0,0 +1,5 @@
1
+ # Cursor Adapter
2
+
3
+ - Use project-local configs as the primary source of style truth.
4
+ - Avoid introducing structure that does not match `src/` and `test/` layout.
5
+ - Validate with `npm run check`.
@@ -0,0 +1,5 @@
1
+ # Windsurf Adapter
2
+
3
+ - Keep edits aligned with @sha3/code-standards exports.
4
+ - Run local enforcement scripts instead of relying on remote CI.
5
+ - Preserve assistant files (`AGENTS.md`, `ai/*.md`) unless explicitly removed by user.
@@ -0,0 +1,6 @@
1
+ # Codex Adapter
2
+
3
+ - Read `AGENTS.md` first and treat all rules as blocking.
4
+ - Do not bypass the single-return policy.
5
+ - Prefer class-based implementations with constructor injection.
6
+ - Always run `npm run check` before finalizing changes.
@@ -0,0 +1,6 @@
1
+ # GitHub Copilot Adapter
2
+
3
+ - Generate class-first code aligned with `AGENTS.md` rules.
4
+ - Keep methods short and focused.
5
+ - Use `async/await` only and typed errors for failure paths.
6
+ - Include test updates for behavior changes.
@@ -0,0 +1,6 @@
1
+ # Cursor Adapter
2
+
3
+ - Apply `AGENTS.md` as the highest-priority local instruction file.
4
+ - Keep feature-folder organization intact.
5
+ - Enforce single-return functions even during quick refactors.
6
+ - Run `npm run check` after modifications.
@@ -0,0 +1,6 @@
1
+ # Windsurf Adapter
2
+
3
+ - Treat `AGENTS.md` as a mandatory contract.
4
+ - Follow class-first and feature-folder conventions.
5
+ - Avoid early returns; keep a single return per function.
6
+ - Execute local deterministic checks with `npm run check`.
@@ -0,0 +1,33 @@
1
+ # AI Coding Contract
2
+
3
+ Project: {{projectName}}
4
+
5
+ This file defines **blocking** rules for AI-generated code in this repository. Every rule below is mandatory.
6
+
7
+ ## Active Profile
8
+
9
+ {{profileSummary}}
10
+
11
+ ## 1. Rule Hierarchy
12
+
13
+ 1. `AGENTS.md` (this file)
14
+ 2. Local project configs (`package.json`, `eslint.config.mjs`, `prettier.config.cjs`, `tsconfig.json`)
15
+ 3. Existing source code and tests
16
+
17
+ If two rules conflict, the higher rule in this list MUST win.
18
+
19
+ ## 2. Code Generation Rules
20
+
21
+ {{codeGenerationRules}}
22
+
23
+ ## 3. Architecture Rules
24
+
25
+ {{architectureRules}}
26
+
27
+ ## 4. Testing and Review Rules
28
+
29
+ {{testingReviewRules}}
30
+
31
+ ## 5. Assistant Execution Notes
32
+
33
+ {{assistantExecutionNotes}}
@@ -0,0 +1,26 @@
1
+ ### Feature-Folder Architecture (MUST)
2
+
3
+ - Code MUST be organized by feature (for example: `src/users`, `src/billing`, `src/invoices`).
4
+ - Each feature MUST keep its own domain model, application services, and infrastructure adapters grouped by feature.
5
+ - Cross-feature imports MUST happen through explicit public entry points.
6
+
7
+ Good example:
8
+
9
+ ```text
10
+ src/
11
+ invoices/
12
+ invoice-service.ts
13
+ invoice-repository.ts
14
+ invoice-types.ts
15
+ billing/
16
+ billing-service.ts
17
+ ```
18
+
19
+ Bad example:
20
+
21
+ ```text
22
+ src/
23
+ services/
24
+ repositories/
25
+ models/
26
+ ```
@@ -0,0 +1,23 @@
1
+ ### Async Policy (MUST)
2
+
3
+ - Asynchronous code MUST use `async/await`.
4
+ - `.then()`/`.catch()` chains are not allowed for new code.
5
+ - Every async call chain MUST have explicit error handling at the boundary.
6
+
7
+ Good example:
8
+
9
+ ```ts
10
+ public async execute(command: SyncInvoicesCommand): Promise<SyncResult> {
11
+ const invoices: Invoice[] = await this.source.fetch(command.accountId);
12
+ const result: SyncResult = await this.writer.persist(invoices);
13
+ return result;
14
+ }
15
+ ```
16
+
17
+ Bad example:
18
+
19
+ ```ts
20
+ public execute(command: SyncInvoicesCommand): Promise<SyncResult> {
21
+ return this.source.fetch(command.accountId).then((invoices) => this.writer.persist(invoices));
22
+ }
23
+ ```
@@ -0,0 +1,96 @@
1
+ ### Class-First Design (MUST)
2
+
3
+ - New business/domain logic MUST be modeled with classes by default.
4
+ - Each class MUST use constructor injection for dependencies.
5
+ - A source file MUST expose one public class.
6
+ - Mutable shared state MUST be avoided; prefer `readonly` fields and deterministic methods.
7
+ - Class-oriented files MUST always include all section comment blocks using this exact format:
8
+ - `/** @section imports:externals */`
9
+ - `/** @section imports:internals */`
10
+ - `/** @section consts */`
11
+ - `/** @section types */`
12
+ - `/** @section private:attributes */`
13
+ - `/** @section private:properties */`
14
+ - `/** @section public:properties */`
15
+ - `/** @section constructor */`
16
+ - `/** @section static:properties */`
17
+ - `/** @section factory */`
18
+ - `/** @section private:methods */`
19
+ - `/** @section public:methods */`
20
+ - `/** @section static:methods */`
21
+ - All section blocks MUST exist even when empty, using `// empty` after the section marker.
22
+ - `factory` MUST only contain methods that create and return instances of the same class.
23
+
24
+ Good example:
25
+
26
+ ```ts
27
+ /** @section imports:externals */
28
+ import { randomUUID } from "node:crypto";
29
+
30
+ /** @section imports:internals */
31
+ import type { InvoiceRepository } from "./invoice-repository.js";
32
+ import type { CreateInvoiceCommand, Invoice } from "./invoice-types.js";
33
+
34
+ /** @section consts */
35
+ const SERVICE_NAME = "invoice-service";
36
+
37
+ /** @section types */
38
+ type InvoiceDraft = { customerId: string; amount: number };
39
+
40
+ export class InvoiceService {
41
+ /** @section private:attributes */
42
+ private readonly requestId: string;
43
+
44
+ /** @section private:properties */
45
+ private readonly repository: InvoiceRepository;
46
+
47
+ /** @section public:properties */
48
+ public readonly serviceName: string;
49
+
50
+ /** @section constructor */
51
+ public constructor(repository: InvoiceRepository) {
52
+ this.repository = repository;
53
+ this.requestId = randomUUID();
54
+ this.serviceName = SERVICE_NAME;
55
+ }
56
+
57
+ /** @section static:properties */
58
+ // empty
59
+
60
+ /** @section factory */
61
+ public static create(repository: InvoiceRepository): InvoiceService {
62
+ const service = new InvoiceService(repository);
63
+ return service;
64
+ }
65
+
66
+ /** @section private:methods */
67
+ private toInvoiceDraft(command: CreateInvoiceCommand): InvoiceDraft {
68
+ const draft: InvoiceDraft = { customerId: command.customerId, amount: command.amount };
69
+ return draft;
70
+ }
71
+
72
+ /** @section public:methods */
73
+ public async create(command: CreateInvoiceCommand): Promise<Invoice> {
74
+ const draft: InvoiceDraft = this.toInvoiceDraft(command);
75
+ const invoice: Invoice = await this.repository.save(draft);
76
+ return invoice;
77
+ }
78
+
79
+ /** @section static:methods */
80
+ // empty
81
+ }
82
+ ```
83
+
84
+ Bad example:
85
+
86
+ ```ts
87
+ import { randomUUID } from "node:crypto";
88
+
89
+ export class InvoiceService {
90
+ public async create(command: CreateInvoiceCommand): Promise<Invoice> {
91
+ const repository = new InvoiceRepository();
92
+ if (!command.customerId) return Promise.reject(new Error("invalid"));
93
+ return repository.save(command as any);
94
+ }
95
+ }
96
+ ```
@@ -0,0 +1,19 @@
1
+ ### Control Flow Braces (MUST)
2
+
3
+ - `if`, `else if`, `else`, `for`, `while`, and `do` blocks MUST always use braces.
4
+ - Single-line `if` statements without braces are forbidden.
5
+ - This rule applies even when the body has one statement.
6
+
7
+ Good example:
8
+
9
+ ```ts
10
+ if (isEnabled) {
11
+ executeTask();
12
+ }
13
+ ```
14
+
15
+ Bad example:
16
+
17
+ ```ts
18
+ if (isEnabled) executeTask();
19
+ ```
@@ -0,0 +1,22 @@
1
+ ### Error Handling (MUST)
2
+
3
+ - Domain/application errors MUST use explicit typed error classes.
4
+ - Errors MUST be thrown and handled at clear application boundaries.
5
+ - Error messages MUST include actionable context.
6
+
7
+ Good example:
8
+
9
+ ```ts
10
+ export class InvoiceNotFoundError extends Error {
11
+ public constructor(invoiceId: string) {
12
+ super(`Invoice not found: ${invoiceId}`);
13
+ this.name = "InvoiceNotFoundError";
14
+ }
15
+ }
16
+ ```
17
+
18
+ Bad example:
19
+
20
+ ```ts
21
+ throw new Error("Oops");
22
+ ```