@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.
- package/AGENTS.md +19 -0
- package/README.md +368 -0
- package/ai/adapters/codex.md +5 -0
- package/ai/adapters/copilot.md +5 -0
- package/ai/adapters/cursor.md +5 -0
- package/ai/adapters/windsurf.md +6 -0
- package/ai/constitution.md +9 -0
- package/bin/code-standards.mjs +1010 -0
- package/eslint/base.mjs +36 -0
- package/eslint/node.mjs +14 -0
- package/eslint/test.mjs +19 -0
- package/index.mjs +19 -0
- package/package.json +64 -0
- package/prettier/index.cjs +10 -0
- package/profiles/default.profile.json +38 -0
- package/profiles/schema.json +159 -0
- package/resources/ai/AGENTS.md +16 -0
- package/resources/ai/adapters/codex.md +5 -0
- package/resources/ai/adapters/copilot.md +5 -0
- package/resources/ai/adapters/cursor.md +5 -0
- package/resources/ai/adapters/windsurf.md +5 -0
- package/resources/ai/templates/adapters/codex.template.md +6 -0
- package/resources/ai/templates/adapters/copilot.template.md +6 -0
- package/resources/ai/templates/adapters/cursor.template.md +6 -0
- package/resources/ai/templates/adapters/windsurf.template.md +6 -0
- package/resources/ai/templates/agents.project.template.md +33 -0
- package/resources/ai/templates/rules/architecture.md +26 -0
- package/resources/ai/templates/rules/async.md +23 -0
- package/resources/ai/templates/rules/class-first.md +96 -0
- package/resources/ai/templates/rules/control-flow.md +19 -0
- package/resources/ai/templates/rules/errors.md +22 -0
- package/resources/ai/templates/rules/functions.md +27 -0
- package/resources/ai/templates/rules/readme.md +25 -0
- package/resources/ai/templates/rules/returns.md +33 -0
- package/resources/ai/templates/rules/testing.md +26 -0
- package/standards/architecture.md +24 -0
- package/standards/changelog-policy.md +12 -0
- package/standards/manifest.json +47 -0
- package/standards/readme.md +25 -0
- package/standards/schema.json +122 -0
- package/standards/style.md +48 -0
- package/standards/testing.md +18 -0
- package/standards/tooling.md +32 -0
- package/templates/node-lib/eslint.config.mjs +4 -0
- package/templates/node-lib/package.json +30 -0
- package/templates/node-lib/prettier.config.cjs +9 -0
- package/templates/node-lib/src/index.ts +3 -0
- package/templates/node-lib/test/smoke.test.ts +8 -0
- package/templates/node-lib/tsconfig.build.json +7 -0
- package/templates/node-lib/tsconfig.json +7 -0
- package/templates/node-service/eslint.config.mjs +4 -0
- package/templates/node-service/package.json +25 -0
- package/templates/node-service/prettier.config.cjs +9 -0
- package/templates/node-service/src/index.ts +17 -0
- package/templates/node-service/test/smoke.test.ts +37 -0
- package/templates/node-service/tsconfig.json +7 -0
- package/tsconfig/base.json +16 -0
- package/tsconfig/node-lib.json +12 -0
- package/tsconfig/node-service.json +10 -0
package/eslint/base.mjs
ADDED
|
@@ -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
|
+
);
|
package/eslint/node.mjs
ADDED
package/eslint/test.mjs
ADDED
|
@@ -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,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,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
|
+
```
|