@sha3/code-standards 0.1.2 → 0.1.4
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/README.md +8 -0
- package/bin/code-standards.mjs +23 -81
- package/eslint/test.mjs +1 -5
- package/package.json +1 -1
- package/prettier/index.cjs +1 -1
- package/profiles/default.profile.json +2 -0
- package/profiles/schema.json +7 -7
- package/resources/ai/templates/examples/demo/src/billing/billing-service.ts +18 -3
- package/resources/ai/templates/examples/demo/src/config.ts +3 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-errors.ts +12 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-service.ts +14 -1
- package/resources/ai/templates/examples/rules/async-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/async-good.ts +12 -0
- package/resources/ai/templates/examples/rules/class-first-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/class-first-good.ts +12 -0
- package/resources/ai/templates/examples/rules/constructor-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/constructor-good.ts +12 -0
- package/resources/ai/templates/examples/rules/control-flow-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/control-flow-good.ts +12 -0
- package/resources/ai/templates/examples/rules/errors-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/errors-good.ts +12 -0
- package/resources/ai/templates/examples/rules/functions-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/functions-good.ts +12 -0
- package/resources/ai/templates/examples/rules/returns-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/returns-good.ts +12 -0
- package/resources/ai/templates/examples/rules/testing-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/testing-good.ts +12 -0
- package/resources/ai/templates/rules/architecture.md +2 -0
- package/resources/ai/templates/rules/class-first.md +2 -0
- package/resources/ai/templates/rules/functions.md +3 -0
- package/resources/ai/templates/rules/testing.md +1 -0
- package/standards/architecture.md +3 -2
- package/standards/manifest.json +1 -1
- package/standards/schema.json +2 -11
- package/standards/style.md +14 -9
- package/standards/testing.md +1 -1
- package/templates/node-lib/src/config.ts +1 -0
- package/templates/node-lib/src/index.ts +3 -1
- package/templates/node-service/src/config.ts +3 -0
- package/templates/node-service/src/index.ts +4 -3
- package/templates/node-service/test/smoke.test.ts +1 -1
package/README.md
CHANGED
|
@@ -53,12 +53,14 @@ Default generated contract includes structural class/file blocks such as:
|
|
|
53
53
|
- `consts`
|
|
54
54
|
- `types`
|
|
55
55
|
- `private:attributes`
|
|
56
|
+
- `protected:attributes`
|
|
56
57
|
- `private:properties`
|
|
57
58
|
- `public:properties`
|
|
58
59
|
- `constructor`
|
|
59
60
|
- `static:properties`
|
|
60
61
|
- `factory`
|
|
61
62
|
- `private:methods`
|
|
63
|
+
- `protected:methods`
|
|
62
64
|
- `public:methods`
|
|
63
65
|
- `static:methods`
|
|
64
66
|
|
|
@@ -72,6 +74,7 @@ All blocks MUST exist even when empty (`// empty`).
|
|
|
72
74
|
|
|
73
75
|
Additional blocking defaults:
|
|
74
76
|
|
|
77
|
+
- Max line length is 160 chars, and lines should stay on one line when they fit.
|
|
75
78
|
- `if`/`else`/loop statements MUST always use braces.
|
|
76
79
|
- README updates MUST follow a top-tier quality standard (see `standards/readme.md`).
|
|
77
80
|
|
|
@@ -107,17 +110,21 @@ After `init`, your new repo contains:
|
|
|
107
110
|
- `ai/codex.md`, `ai/cursor.md`, `ai/copilot.md`, `ai/windsurf.md`
|
|
108
111
|
- `ai/examples/rules/*.ts` (good/bad examples per rule)
|
|
109
112
|
- `ai/examples/demo/src/*` (feature-folder demo with classes and section blocks)
|
|
113
|
+
- `src/config.ts` for centralized hardcoded configuration values
|
|
110
114
|
- `.gitignore` preconfigured for Node/TypeScript output
|
|
111
115
|
- lint/format/typecheck/test-ready project template
|
|
112
116
|
|
|
113
117
|
That means the next step is **not** configuring tools. The next step is telling your assistant to obey `AGENTS.md` before coding.
|
|
114
118
|
|
|
119
|
+
Generated project code is TypeScript-only: implementation and tests live in `.ts` files.
|
|
120
|
+
|
|
115
121
|
## TypeScript Example Files
|
|
116
122
|
|
|
117
123
|
`init` now stores code examples in `.ts` files instead of embedding them inside `AGENTS.md`.
|
|
118
124
|
|
|
119
125
|
Demo structure generated by default:
|
|
120
126
|
|
|
127
|
+
- `ai/examples/demo/src/config.ts`
|
|
121
128
|
- `ai/examples/demo/src/invoices/invoice-service.ts`
|
|
122
129
|
- `ai/examples/demo/src/invoices/invoice-errors.ts`
|
|
123
130
|
- `ai/examples/demo/src/invoices/invoice-types.ts`
|
|
@@ -278,6 +285,7 @@ Commands:
|
|
|
278
285
|
### `init` options
|
|
279
286
|
|
|
280
287
|
`init` always uses the current working directory as target.
|
|
288
|
+
An existing `.git/` directory is allowed without `--force`.
|
|
281
289
|
|
|
282
290
|
- `--template <node-lib|node-service>`
|
|
283
291
|
- `--yes`
|
package/bin/code-standards.mjs
CHANGED
|
@@ -55,12 +55,14 @@ const DEFAULT_PROFILE = {
|
|
|
55
55
|
"consts",
|
|
56
56
|
"types",
|
|
57
57
|
"private:attributes",
|
|
58
|
+
"protected:attributes",
|
|
58
59
|
"private:properties",
|
|
59
60
|
"public:properties",
|
|
60
61
|
"constructor",
|
|
61
62
|
"static:properties",
|
|
62
63
|
"factory",
|
|
63
64
|
"private:methods",
|
|
65
|
+
"protected:methods",
|
|
64
66
|
"public:methods",
|
|
65
67
|
"static:methods"
|
|
66
68
|
],
|
|
@@ -88,11 +90,7 @@ const PROFILE_QUESTIONS = [
|
|
|
88
90
|
{
|
|
89
91
|
key: "return_policy",
|
|
90
92
|
prompt: "Return policy",
|
|
91
|
-
options: [
|
|
92
|
-
"single_return_strict_no_exceptions",
|
|
93
|
-
"single_return_with_guard_clauses",
|
|
94
|
-
"free_return_style"
|
|
95
|
-
]
|
|
93
|
+
options: ["single_return_strict_no_exceptions", "single_return_with_guard_clauses", "free_return_style"]
|
|
96
94
|
},
|
|
97
95
|
{
|
|
98
96
|
key: "class_design",
|
|
@@ -202,9 +200,7 @@ function parseInitArgs(argv) {
|
|
|
202
200
|
const token = argv[i];
|
|
203
201
|
|
|
204
202
|
if (!token.startsWith("-")) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`Positional project names are not supported: ${token}. Run init from your target directory.`
|
|
207
|
-
);
|
|
203
|
+
throw new Error(`Positional project names are not supported: ${token}. Run init from your target directory.`);
|
|
208
204
|
}
|
|
209
205
|
|
|
210
206
|
if (token === "--template") {
|
|
@@ -394,11 +390,10 @@ async function ensureTargetReady(targetPath, force) {
|
|
|
394
390
|
}
|
|
395
391
|
|
|
396
392
|
const entries = await readdir(targetPath);
|
|
393
|
+
const nonGitEntries = entries.filter((entry) => entry !== ".git");
|
|
397
394
|
|
|
398
|
-
if (
|
|
399
|
-
throw new Error(
|
|
400
|
-
`Target directory is not empty: ${targetPath}. Use --force to continue and overwrite files.`
|
|
401
|
-
);
|
|
395
|
+
if (nonGitEntries.length > 0 && !force) {
|
|
396
|
+
throw new Error(`Target directory is not empty: ${targetPath}. Use --force to continue and overwrite files.`);
|
|
402
397
|
}
|
|
403
398
|
}
|
|
404
399
|
|
|
@@ -455,9 +450,7 @@ function validateProfile(profile, schema, sourceLabel) {
|
|
|
455
450
|
return;
|
|
456
451
|
}
|
|
457
452
|
|
|
458
|
-
const details = (validate.errors ?? [])
|
|
459
|
-
.map((issue) => `${issue.instancePath || "/"}: ${issue.message ?? "invalid"}`)
|
|
460
|
-
.join("; ");
|
|
453
|
+
const details = (validate.errors ?? []).map((issue) => `${issue.instancePath || "/"}: ${issue.message ?? "invalid"}`).join("; ");
|
|
461
454
|
|
|
462
455
|
throw new Error(`Invalid profile at ${sourceLabel}: ${details}`);
|
|
463
456
|
}
|
|
@@ -549,12 +542,7 @@ async function createProfileInteractively(baseProfile) {
|
|
|
549
542
|
|
|
550
543
|
try {
|
|
551
544
|
for (const question of PROFILE_QUESTIONS) {
|
|
552
|
-
profile[question.key] = await askChoice(
|
|
553
|
-
rl,
|
|
554
|
-
question.prompt,
|
|
555
|
-
question.options,
|
|
556
|
-
profile[question.key]
|
|
557
|
-
);
|
|
545
|
+
profile[question.key] = await askChoice(rl, question.prompt, question.options, profile[question.key]);
|
|
558
546
|
}
|
|
559
547
|
} finally {
|
|
560
548
|
rl.close();
|
|
@@ -572,9 +560,7 @@ async function runProfile(rawOptions) {
|
|
|
572
560
|
const packageRoot = resolvePackageRoot();
|
|
573
561
|
const schema = await loadProfileSchema(packageRoot);
|
|
574
562
|
const defaultProfilePath = getBundledProfilePath(packageRoot);
|
|
575
|
-
const outputPath = rawOptions.profilePath
|
|
576
|
-
? path.resolve(process.cwd(), rawOptions.profilePath)
|
|
577
|
-
: defaultProfilePath;
|
|
563
|
+
const outputPath = rawOptions.profilePath ? path.resolve(process.cwd(), rawOptions.profilePath) : defaultProfilePath;
|
|
578
564
|
const shouldUseNonInteractive = rawOptions.nonInteractive || !process.stdin.isTTY;
|
|
579
565
|
const exists = await pathExists(outputPath);
|
|
580
566
|
|
|
@@ -589,11 +575,7 @@ async function runProfile(rawOptions) {
|
|
|
589
575
|
});
|
|
590
576
|
|
|
591
577
|
try {
|
|
592
|
-
const shouldOverwrite = await promptYesNo(
|
|
593
|
-
rl,
|
|
594
|
-
`Profile already exists at ${outputPath}. Overwrite?`,
|
|
595
|
-
false
|
|
596
|
-
);
|
|
578
|
+
const shouldOverwrite = await promptYesNo(rl, `Profile already exists at ${outputPath}. Overwrite?`, false);
|
|
597
579
|
|
|
598
580
|
if (!shouldOverwrite) {
|
|
599
581
|
console.log("Profile update cancelled.");
|
|
@@ -655,44 +637,30 @@ function buildAlternativeRules(profile) {
|
|
|
655
637
|
|
|
656
638
|
if (profile.return_policy !== "single_return_strict_no_exceptions") {
|
|
657
639
|
codeRules.push(
|
|
658
|
-
"### Return Policy Override (MUST)\n\n" +
|
|
659
|
-
`- The active return policy is \`${profile.return_policy}\` and MUST be respected for all new functions.`
|
|
640
|
+
"### Return Policy Override (MUST)\n\n" + `- The active return policy is \`${profile.return_policy}\` and MUST be respected for all new functions.`
|
|
660
641
|
);
|
|
661
642
|
}
|
|
662
643
|
|
|
663
644
|
if (profile.function_size_policy !== "max_30_lines_soft") {
|
|
664
|
-
codeRules.push(
|
|
665
|
-
"### Function Size Override (MUST)\n\n" +
|
|
666
|
-
`- The active function-size policy is \`${profile.function_size_policy}\` and MUST be enforced.`
|
|
667
|
-
);
|
|
645
|
+
codeRules.push("### Function Size Override (MUST)\n\n" + `- The active function-size policy is \`${profile.function_size_policy}\` and MUST be enforced.`);
|
|
668
646
|
}
|
|
669
647
|
|
|
670
648
|
if (profile.error_handling !== "exceptions_with_typed_errors") {
|
|
671
|
-
codeRules.push(
|
|
672
|
-
"### Error Handling Override (MUST)\n\n" +
|
|
673
|
-
`- The active error-handling policy is \`${profile.error_handling}\`.`
|
|
674
|
-
);
|
|
649
|
+
codeRules.push("### Error Handling Override (MUST)\n\n" + `- The active error-handling policy is \`${profile.error_handling}\`.`);
|
|
675
650
|
}
|
|
676
651
|
|
|
677
652
|
if (profile.async_style !== "async_await_only") {
|
|
678
|
-
codeRules.push(
|
|
679
|
-
"### Async Style Override (MUST)\n\n" +
|
|
680
|
-
`- The active async policy is \`${profile.async_style}\` and MUST be followed in new code.`
|
|
681
|
-
);
|
|
653
|
+
codeRules.push("### Async Style Override (MUST)\n\n" + `- The active async policy is \`${profile.async_style}\` and MUST be followed in new code.`);
|
|
682
654
|
}
|
|
683
655
|
|
|
684
656
|
let architectureRule = "";
|
|
685
657
|
if (profile.architecture !== "feature_folders") {
|
|
686
|
-
architectureRule =
|
|
687
|
-
"### Architecture Override (MUST)\n\n" +
|
|
688
|
-
`- The active architecture is \`${profile.architecture}\` and MUST take precedence.`;
|
|
658
|
+
architectureRule = "### Architecture Override (MUST)\n\n" + `- The active architecture is \`${profile.architecture}\` and MUST take precedence.`;
|
|
689
659
|
}
|
|
690
660
|
|
|
691
661
|
let testingRule = "";
|
|
692
662
|
if (profile.testing_policy !== "tests_required_for_behavior_change") {
|
|
693
|
-
testingRule =
|
|
694
|
-
"### Testing Override (MUST)\n\n" +
|
|
695
|
-
`- The active testing policy is \`${profile.testing_policy}\`.`;
|
|
663
|
+
testingRule = "### Testing Override (MUST)\n\n" + `- The active testing policy is \`${profile.testing_policy}\`.`;
|
|
696
664
|
}
|
|
697
665
|
|
|
698
666
|
return {
|
|
@@ -721,14 +689,7 @@ async function buildRuleSections(packageRoot, profile) {
|
|
|
721
689
|
const readmeRule = await readRule("readme.md");
|
|
722
690
|
|
|
723
691
|
const alternatives = buildAlternativeRules(profile);
|
|
724
|
-
const codeGenerationRules = [
|
|
725
|
-
classRule,
|
|
726
|
-
functionRule,
|
|
727
|
-
returnRule,
|
|
728
|
-
controlFlowRule,
|
|
729
|
-
errorRule,
|
|
730
|
-
asyncRule
|
|
731
|
-
];
|
|
692
|
+
const codeGenerationRules = [classRule, functionRule, returnRule, controlFlowRule, errorRule, asyncRule];
|
|
732
693
|
|
|
733
694
|
if (alternatives.codeRules.length > 0) {
|
|
734
695
|
codeGenerationRules.push(...alternatives.codeRules);
|
|
@@ -742,13 +703,7 @@ async function buildRuleSections(packageRoot, profile) {
|
|
|
742
703
|
}
|
|
743
704
|
|
|
744
705
|
async function renderProjectAgents(packageRoot, targetDir, projectName, profile) {
|
|
745
|
-
const templatePath = path.join(
|
|
746
|
-
packageRoot,
|
|
747
|
-
"resources",
|
|
748
|
-
"ai",
|
|
749
|
-
"templates",
|
|
750
|
-
"agents.project.template.md"
|
|
751
|
-
);
|
|
706
|
+
const templatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
|
|
752
707
|
const template = await readFile(templatePath, "utf8");
|
|
753
708
|
const sections = await buildRuleSections(packageRoot, profile);
|
|
754
709
|
|
|
@@ -806,11 +761,7 @@ async function maybeInitializeProfileInteractively(packageRoot, profilePath) {
|
|
|
806
761
|
});
|
|
807
762
|
|
|
808
763
|
try {
|
|
809
|
-
const shouldInit = await promptYesNo(
|
|
810
|
-
rl,
|
|
811
|
-
`Profile not found at ${profilePath}. Initialize it with package defaults?`,
|
|
812
|
-
true
|
|
813
|
-
);
|
|
764
|
+
const shouldInit = await promptYesNo(rl, `Profile not found at ${profilePath}. Initialize it with package defaults?`, true);
|
|
814
765
|
|
|
815
766
|
if (!shouldInit) {
|
|
816
767
|
throw new Error("Profile initialization declined by user.");
|
|
@@ -873,9 +824,7 @@ async function promptForMissing(options) {
|
|
|
873
824
|
const resolved = { ...options };
|
|
874
825
|
|
|
875
826
|
if (!resolved.template) {
|
|
876
|
-
const templateAnswer = await rl.question(
|
|
877
|
-
"Choose template (node-lib/node-service) [node-lib]: "
|
|
878
|
-
);
|
|
827
|
+
const templateAnswer = await rl.question("Choose template (node-lib/node-service) [node-lib]: ");
|
|
879
828
|
const normalized = templateAnswer.trim() || "node-lib";
|
|
880
829
|
|
|
881
830
|
if (!TEMPLATE_NAMES.includes(normalized)) {
|
|
@@ -899,13 +848,7 @@ async function promptForMissing(options) {
|
|
|
899
848
|
|
|
900
849
|
async function validateInitResources(packageRoot, templateName) {
|
|
901
850
|
const templateDir = path.join(packageRoot, "templates", templateName);
|
|
902
|
-
const agentsTemplatePath = path.join(
|
|
903
|
-
packageRoot,
|
|
904
|
-
"resources",
|
|
905
|
-
"ai",
|
|
906
|
-
"templates",
|
|
907
|
-
"agents.project.template.md"
|
|
908
|
-
);
|
|
851
|
+
const agentsTemplatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
|
|
909
852
|
const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
|
|
910
853
|
const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
|
|
911
854
|
|
|
@@ -943,8 +886,7 @@ async function runInit(rawOptions) {
|
|
|
943
886
|
|
|
944
887
|
const targetPath = path.resolve(process.cwd());
|
|
945
888
|
const inferredProjectName = path.basename(targetPath);
|
|
946
|
-
const projectName =
|
|
947
|
-
inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
|
|
889
|
+
const projectName = inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
|
|
948
890
|
const packageName = sanitizePackageName(projectName);
|
|
949
891
|
|
|
950
892
|
await ensureTargetReady(targetPath, options.force);
|
package/eslint/test.mjs
CHANGED
|
@@ -4,11 +4,7 @@ import nodeConfig from "./node.mjs";
|
|
|
4
4
|
export default [
|
|
5
5
|
...nodeConfig,
|
|
6
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
|
-
],
|
|
7
|
+
files: ["**/*.test.{js,mjs,cjs,ts,mts,cts}", "**/*.spec.{js,mjs,cjs,ts,mts,cts}", "test/**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
12
8
|
languageOptions: {
|
|
13
9
|
globals: {
|
|
14
10
|
...globals.mocha,
|
package/package.json
CHANGED
package/prettier/index.cjs
CHANGED
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
"consts",
|
|
19
19
|
"types",
|
|
20
20
|
"private:attributes",
|
|
21
|
+
"protected:attributes",
|
|
21
22
|
"private:properties",
|
|
22
23
|
"public:properties",
|
|
23
24
|
"constructor",
|
|
24
25
|
"static:properties",
|
|
25
26
|
"factory",
|
|
26
27
|
"private:methods",
|
|
28
|
+
"protected:methods",
|
|
27
29
|
"public:methods",
|
|
28
30
|
"static:methods"
|
|
29
31
|
],
|
package/profiles/schema.json
CHANGED
|
@@ -43,11 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"return_policy": {
|
|
45
45
|
"type": "string",
|
|
46
|
-
"enum": [
|
|
47
|
-
"single_return_strict_no_exceptions",
|
|
48
|
-
"single_return_with_guard_clauses",
|
|
49
|
-
"free_return_style"
|
|
50
|
-
]
|
|
46
|
+
"enum": ["single_return_strict_no_exceptions", "single_return_with_guard_clauses", "free_return_style"]
|
|
51
47
|
},
|
|
52
48
|
"class_design": {
|
|
53
49
|
"type": "string",
|
|
@@ -87,8 +83,8 @@
|
|
|
87
83
|
},
|
|
88
84
|
"comment_section_blocks": {
|
|
89
85
|
"type": "array",
|
|
90
|
-
"minItems":
|
|
91
|
-
"maxItems":
|
|
86
|
+
"minItems": 15,
|
|
87
|
+
"maxItems": 15,
|
|
92
88
|
"items": {
|
|
93
89
|
"type": "string",
|
|
94
90
|
"enum": [
|
|
@@ -97,12 +93,14 @@
|
|
|
97
93
|
"consts",
|
|
98
94
|
"types",
|
|
99
95
|
"private:attributes",
|
|
96
|
+
"protected:attributes",
|
|
100
97
|
"private:properties",
|
|
101
98
|
"public:properties",
|
|
102
99
|
"constructor",
|
|
103
100
|
"static:properties",
|
|
104
101
|
"factory",
|
|
105
102
|
"private:methods",
|
|
103
|
+
"protected:methods",
|
|
106
104
|
"public:methods",
|
|
107
105
|
"static:methods"
|
|
108
106
|
]
|
|
@@ -113,12 +111,14 @@
|
|
|
113
111
|
"consts",
|
|
114
112
|
"types",
|
|
115
113
|
"private:attributes",
|
|
114
|
+
"protected:attributes",
|
|
116
115
|
"private:properties",
|
|
117
116
|
"public:properties",
|
|
118
117
|
"constructor",
|
|
119
118
|
"static:properties",
|
|
120
119
|
"factory",
|
|
121
120
|
"private:methods",
|
|
121
|
+
"protected:methods",
|
|
122
122
|
"public:methods",
|
|
123
123
|
"static:methods"
|
|
124
124
|
]
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @section imports:internals
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { BILLING_CURRENCY_SYMBOL, STATUS_SERVICE_URL } from "../config.js";
|
|
11
12
|
import type { InvoiceService } from "../invoices/invoice-service.js";
|
|
12
13
|
import type { InvoiceSummary } from "../invoices/invoice-types.js";
|
|
13
14
|
|
|
@@ -15,7 +16,7 @@ import type { InvoiceSummary } from "../invoices/invoice-types.js";
|
|
|
15
16
|
* @section consts
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// empty
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @section types
|
|
@@ -26,6 +27,7 @@ export type BillingSnapshot = {
|
|
|
26
27
|
invoiceCount: number;
|
|
27
28
|
totalAmount: number;
|
|
28
29
|
formattedTotal: string;
|
|
30
|
+
statusServiceUrl: string;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export class BillingService {
|
|
@@ -35,6 +37,12 @@ export class BillingService {
|
|
|
35
37
|
|
|
36
38
|
// empty
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @section protected:attributes
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// empty
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
47
|
* @section private:properties
|
|
40
48
|
*/
|
|
@@ -75,10 +83,16 @@ export class BillingService {
|
|
|
75
83
|
*/
|
|
76
84
|
|
|
77
85
|
private formatCurrency(amount: number): string {
|
|
78
|
-
const formattedAmount = `${
|
|
86
|
+
const formattedAmount = `${BILLING_CURRENCY_SYMBOL}${amount.toFixed(2)}`;
|
|
79
87
|
return formattedAmount;
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @section protected:methods
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
// empty
|
|
95
|
+
|
|
82
96
|
/**
|
|
83
97
|
* @section public:methods
|
|
84
98
|
*/
|
|
@@ -89,7 +103,8 @@ export class BillingService {
|
|
|
89
103
|
customerId,
|
|
90
104
|
invoiceCount: summary.count,
|
|
91
105
|
totalAmount: summary.totalAmount,
|
|
92
|
-
formattedTotal: this.formatCurrency(summary.totalAmount)
|
|
106
|
+
formattedTotal: this.formatCurrency(summary.totalAmount),
|
|
107
|
+
statusServiceUrl: STATUS_SERVICE_URL
|
|
93
108
|
};
|
|
94
109
|
return snapshot;
|
|
95
110
|
}
|
|
@@ -29,6 +29,12 @@ export class InvalidInvoiceCommandError extends Error {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -72,6 +78,12 @@ export class InvalidInvoiceCommandError extends Error {
|
|
|
72
78
|
|
|
73
79
|
// empty
|
|
74
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @section protected:methods
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
// empty
|
|
86
|
+
|
|
75
87
|
/**
|
|
76
88
|
* @section public:methods
|
|
77
89
|
*/
|
|
@@ -8,6 +8,7 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
* @section imports:internals
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { MINIMUM_INVOICE_AMOUNT } from "../config.js";
|
|
11
12
|
import { InvalidInvoiceCommandError } from "./invoice-errors.js";
|
|
12
13
|
import type { CreateInvoiceCommand, Invoice, InvoiceSummary } from "./invoice-types.js";
|
|
13
14
|
|
|
@@ -15,7 +16,7 @@ import type { CreateInvoiceCommand, Invoice, InvoiceSummary } from "./invoice-ty
|
|
|
15
16
|
* @section consts
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// empty
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @section types
|
|
@@ -30,6 +31,12 @@ export class InvoiceService {
|
|
|
30
31
|
|
|
31
32
|
// empty
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @section protected:attributes
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// empty
|
|
39
|
+
|
|
33
40
|
/**
|
|
34
41
|
* @section private:properties
|
|
35
42
|
*/
|
|
@@ -89,6 +96,12 @@ export class InvoiceService {
|
|
|
89
96
|
return invoice;
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* @section protected:methods
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
// empty
|
|
104
|
+
|
|
92
105
|
/**
|
|
93
106
|
* @section public:methods
|
|
94
107
|
*/
|
|
@@ -33,6 +33,12 @@ export class InvoiceSyncService {
|
|
|
33
33
|
|
|
34
34
|
// empty
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @section protected:attributes
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// empty
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* @section private:properties
|
|
38
44
|
*/
|
|
@@ -76,6 +82,12 @@ export class InvoiceSyncService {
|
|
|
76
82
|
|
|
77
83
|
// empty
|
|
78
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @section protected:methods
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
// empty
|
|
90
|
+
|
|
79
91
|
/**
|
|
80
92
|
* @section public:methods
|
|
81
93
|
*/
|
|
@@ -33,6 +33,12 @@ export class InvoiceSyncService {
|
|
|
33
33
|
|
|
34
34
|
// empty
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @section protected:attributes
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// empty
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* @section private:properties
|
|
38
44
|
*/
|
|
@@ -76,6 +82,12 @@ export class InvoiceSyncService {
|
|
|
76
82
|
|
|
77
83
|
// empty
|
|
78
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @section protected:methods
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
// empty
|
|
90
|
+
|
|
79
91
|
/**
|
|
80
92
|
* @section public:methods
|
|
81
93
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceService {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -69,6 +75,12 @@ export class InvoiceService {
|
|
|
69
75
|
|
|
70
76
|
// empty
|
|
71
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @section protected:methods
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
// empty
|
|
83
|
+
|
|
72
84
|
/**
|
|
73
85
|
* @section public:methods
|
|
74
86
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceService {
|
|
|
30
30
|
|
|
31
31
|
private readonly requestId: string;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -81,6 +87,12 @@ export class InvoiceService {
|
|
|
81
87
|
return invoice;
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @section protected:methods
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
// empty
|
|
95
|
+
|
|
84
96
|
/**
|
|
85
97
|
* @section public:methods
|
|
86
98
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceIdBuilder {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -79,6 +85,12 @@ export class InvoiceIdBuilder {
|
|
|
79
85
|
return year;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @section protected:methods
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
// empty
|
|
93
|
+
|
|
82
94
|
/**
|
|
83
95
|
* @section public:methods
|
|
84
96
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceIdBuilder {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -78,6 +84,12 @@ export class InvoiceIdBuilder {
|
|
|
78
84
|
return year;
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @section protected:methods
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
// empty
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* @section public:methods
|
|
83
95
|
*/
|
|
@@ -29,6 +29,12 @@ export class ControlFlowPolicy {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class ControlFlowPolicy {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class FeatureGate {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -70,6 +76,12 @@ export class FeatureGate {
|
|
|
70
76
|
|
|
71
77
|
// empty
|
|
72
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @section protected:methods
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
// empty
|
|
84
|
+
|
|
73
85
|
/**
|
|
74
86
|
* @section public:methods
|
|
75
87
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceLookup {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceLookup {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceNotFoundError extends Error {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -72,6 +78,12 @@ export class InvoiceNotFoundError extends Error {
|
|
|
72
78
|
|
|
73
79
|
// empty
|
|
74
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @section protected:methods
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
// empty
|
|
86
|
+
|
|
75
87
|
/**
|
|
76
88
|
* @section public:methods
|
|
77
89
|
*/
|
|
@@ -30,6 +30,12 @@ export class PaymentNormalizer {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -69,6 +75,12 @@ export class PaymentNormalizer {
|
|
|
69
75
|
|
|
70
76
|
// empty
|
|
71
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @section protected:methods
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
// empty
|
|
83
|
+
|
|
72
84
|
/**
|
|
73
85
|
* @section public:methods
|
|
74
86
|
*/
|
|
@@ -30,6 +30,12 @@ export class PaymentNormalizer {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -82,6 +88,12 @@ export class PaymentNormalizer {
|
|
|
82
88
|
return sanitizedMetadata;
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @section protected:methods
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
// empty
|
|
96
|
+
|
|
85
97
|
/**
|
|
86
98
|
* @section public:methods
|
|
87
99
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceStatusPresenter {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceStatusPresenter {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceStatusPresenter {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceStatusPresenter {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceEscalationPolicy {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceEscalationPolicy {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceEscalationPolicy {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -73,6 +79,12 @@ export class InvoiceEscalationPolicy {
|
|
|
73
79
|
return dayCount;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @section protected:methods
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
// empty
|
|
87
|
+
|
|
76
88
|
/**
|
|
77
89
|
* @section public:methods
|
|
78
90
|
*/
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
- Code MUST be organized by feature (for example: `src/users`, `src/billing`, `src/invoices`).
|
|
4
4
|
- Each feature MUST keep its own domain model, application services, and infrastructure adapters grouped by feature.
|
|
5
5
|
- Cross-feature imports MUST happen through explicit public entry points.
|
|
6
|
+
- Hardcoded, non-parameterized configuration MUST be centralized in `src/config.ts` (for example, external service URLs).
|
|
6
7
|
|
|
7
8
|
Good example:
|
|
8
9
|
|
|
10
|
+
- `ai/examples/demo/src/config.ts`
|
|
9
11
|
- `ai/examples/demo/src/invoices/invoice-service.ts`
|
|
10
12
|
- `ai/examples/demo/src/invoices/invoice-errors.ts`
|
|
11
13
|
- `ai/examples/demo/src/invoices/invoice-types.ts`
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
- `consts`
|
|
15
15
|
- `types`
|
|
16
16
|
- `private:attributes`
|
|
17
|
+
- `protected:attributes`
|
|
17
18
|
- `private:properties`
|
|
18
19
|
- `public:properties`
|
|
19
20
|
- `constructor`
|
|
20
21
|
- `static:properties`
|
|
21
22
|
- `factory`
|
|
22
23
|
- `private:methods`
|
|
24
|
+
- `protected:methods`
|
|
23
25
|
- `public:methods`
|
|
24
26
|
- `static:methods`
|
|
25
27
|
- All section blocks MUST exist even when empty, using `// empty` after the section marker.
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
- Functions MUST implement one responsibility.
|
|
5
5
|
- Long functions MUST be split into private helper methods.
|
|
6
6
|
- Types MUST be preferred over interfaces for local modeling unless a public contract requires an interface.
|
|
7
|
+
- New implementation and test files MUST be `.ts`.
|
|
8
|
+
- JavaScript source files (`.js`, `.mjs`, `.cjs`) are not allowed in `src/` or `test/`.
|
|
9
|
+
- If a declaration/expression fits in one line within 160 chars, it MUST stay on one line.
|
|
7
10
|
|
|
8
11
|
Good example:
|
|
9
12
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Any behavior change MUST include or update tests.
|
|
4
4
|
- Tests MUST validate behavior, not implementation details.
|
|
5
|
+
- Test files MUST be TypeScript (`*.test.ts`).
|
|
5
6
|
- Comments MUST be explicit and extensive when logic is non-trivial.
|
|
6
7
|
- Async workflows MUST use `async/await` style only.
|
|
7
8
|
|
|
@@ -14,11 +14,12 @@ Two templates are supported:
|
|
|
14
14
|
Both templates SHOULD keep this baseline structure:
|
|
15
15
|
|
|
16
16
|
- `src/` for implementation.
|
|
17
|
+
- `src/config.ts` for non-parameterized hardcoded configuration values.
|
|
17
18
|
- `test/` for automated tests.
|
|
18
|
-
- `eslint
|
|
19
|
+
- Root tooling config files (`eslint`, `prettier`, `tsconfig`) at project root.
|
|
19
20
|
|
|
20
21
|
## Boundary Rules
|
|
21
22
|
|
|
22
23
|
- Keep feature modules cohesive.
|
|
23
24
|
- Avoid cyclic dependencies.
|
|
24
|
-
- Keep configuration centralized
|
|
25
|
+
- Keep hardcoded application configuration centralized in `src/config.ts`.
|
package/standards/manifest.json
CHANGED
package/standards/schema.json
CHANGED
|
@@ -4,16 +4,7 @@
|
|
|
4
4
|
"title": "Code Standards Manifest",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": [
|
|
8
|
-
"meta",
|
|
9
|
-
"language",
|
|
10
|
-
"formatting",
|
|
11
|
-
"naming",
|
|
12
|
-
"architecture",
|
|
13
|
-
"testing",
|
|
14
|
-
"tooling",
|
|
15
|
-
"ai"
|
|
16
|
-
],
|
|
7
|
+
"required": ["meta", "language", "formatting", "naming", "architecture", "testing", "tooling", "ai"],
|
|
17
8
|
"properties": {
|
|
18
9
|
"meta": {
|
|
19
10
|
"type": "object",
|
|
@@ -42,7 +33,7 @@
|
|
|
42
33
|
"required": ["prettier", "lineLength", "semi", "singleQuote"],
|
|
43
34
|
"properties": {
|
|
44
35
|
"prettier": { "type": "boolean" },
|
|
45
|
-
"lineLength": { "type": "integer", "minimum": 80, "maximum":
|
|
36
|
+
"lineLength": { "type": "integer", "minimum": 80, "maximum": 200 },
|
|
46
37
|
"semi": { "type": "boolean" },
|
|
47
38
|
"singleQuote": { "type": "boolean" }
|
|
48
39
|
}
|
package/standards/style.md
CHANGED
|
@@ -5,13 +5,16 @@ All code MUST follow the canonical rules in `standards/manifest.json`.
|
|
|
5
5
|
## Formatting
|
|
6
6
|
|
|
7
7
|
- Use Prettier for formatting.
|
|
8
|
-
- Use a max line length of
|
|
8
|
+
- Use a max line length of 160.
|
|
9
|
+
- Keep lines as compact as possible: if a declaration/expression fits in one line within 160 chars, keep it on one line.
|
|
9
10
|
- Use semicolons.
|
|
10
11
|
- Use double quotes for strings.
|
|
11
12
|
|
|
12
13
|
## TypeScript
|
|
13
14
|
|
|
14
15
|
- Use strict TypeScript mode.
|
|
16
|
+
- Project implementation and tests MUST be TypeScript-only (`.ts`).
|
|
17
|
+
- JavaScript source files (`.js`, `.mjs`, `.cjs`) are not allowed in `src/` or `test/`.
|
|
15
18
|
- Avoid `any` unless there is no viable alternative.
|
|
16
19
|
- Prefer explicit return types for exported functions.
|
|
17
20
|
- Use type-only imports when possible.
|
|
@@ -40,14 +43,16 @@ Required block names:
|
|
|
40
43
|
3. `consts`
|
|
41
44
|
4. `types`
|
|
42
45
|
5. `private:attributes`
|
|
43
|
-
6. `
|
|
44
|
-
7. `
|
|
45
|
-
8. `
|
|
46
|
-
9. `
|
|
47
|
-
10. `
|
|
48
|
-
11. `
|
|
49
|
-
12. `
|
|
50
|
-
13. `
|
|
46
|
+
6. `protected:attributes`
|
|
47
|
+
7. `private:properties`
|
|
48
|
+
8. `public:properties`
|
|
49
|
+
9. `constructor`
|
|
50
|
+
10. `static:properties`
|
|
51
|
+
11. `factory`
|
|
52
|
+
12. `private:methods`
|
|
53
|
+
13. `protected:methods`
|
|
54
|
+
14. `public:methods`
|
|
55
|
+
15. `static:methods`
|
|
51
56
|
|
|
52
57
|
All section blocks MUST be present even when empty. Empty sections MUST include:
|
|
53
58
|
|
package/standards/testing.md
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const GREETING_PREFIX = "Hello";
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { DEFAULT_PORT, EXTERNAL_STATUS_URL, RESPONSE_CONTENT_TYPE } from "./config.js";
|
|
2
3
|
|
|
3
4
|
export function buildServer() {
|
|
4
5
|
return createServer((_, response) => {
|
|
5
6
|
response.statusCode = 200;
|
|
6
|
-
response.setHeader("content-type",
|
|
7
|
-
response.end(JSON.stringify({ ok: true }));
|
|
7
|
+
response.setHeader("content-type", RESPONSE_CONTENT_TYPE);
|
|
8
|
+
response.end(JSON.stringify({ ok: true, statusSource: EXTERNAL_STATUS_URL }));
|
|
8
9
|
});
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
if (process.env.NODE_ENV !== "test") {
|
|
12
|
-
const port = Number(process.env.PORT ??
|
|
13
|
+
const port = Number(process.env.PORT ?? String(DEFAULT_PORT));
|
|
13
14
|
const server = buildServer();
|
|
14
15
|
server.listen(port, () => {
|
|
15
16
|
console.log(`service listening on http://localhost:${port}`);
|
|
@@ -22,7 +22,7 @@ test("service responds with ok payload", async () => {
|
|
|
22
22
|
const json = await response.json();
|
|
23
23
|
|
|
24
24
|
assert.equal(response.status, 200);
|
|
25
|
-
assert.deepEqual(json, { ok: true });
|
|
25
|
+
assert.deepEqual(json, { ok: true, statusSource: "https://status.example.com/health" });
|
|
26
26
|
|
|
27
27
|
await new Promise<void>((resolve, reject) => {
|
|
28
28
|
server.close((error) => {
|