@sha3/code-standards 0.1.3 → 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.
Files changed (39) hide show
  1. package/README.md +5 -0
  2. package/bin/code-standards.mjs +21 -80
  3. package/eslint/test.mjs +1 -5
  4. package/package.json +1 -1
  5. package/prettier/index.cjs +1 -1
  6. package/profiles/default.profile.json +2 -0
  7. package/profiles/schema.json +7 -7
  8. package/resources/ai/templates/examples/demo/src/billing/billing-service.ts +18 -3
  9. package/resources/ai/templates/examples/demo/src/config.ts +3 -0
  10. package/resources/ai/templates/examples/demo/src/invoices/invoice-errors.ts +12 -0
  11. package/resources/ai/templates/examples/demo/src/invoices/invoice-service.ts +14 -1
  12. package/resources/ai/templates/examples/rules/async-bad.ts +12 -0
  13. package/resources/ai/templates/examples/rules/async-good.ts +12 -0
  14. package/resources/ai/templates/examples/rules/class-first-bad.ts +12 -0
  15. package/resources/ai/templates/examples/rules/class-first-good.ts +12 -0
  16. package/resources/ai/templates/examples/rules/constructor-bad.ts +12 -0
  17. package/resources/ai/templates/examples/rules/constructor-good.ts +12 -0
  18. package/resources/ai/templates/examples/rules/control-flow-bad.ts +12 -0
  19. package/resources/ai/templates/examples/rules/control-flow-good.ts +12 -0
  20. package/resources/ai/templates/examples/rules/errors-bad.ts +12 -0
  21. package/resources/ai/templates/examples/rules/errors-good.ts +12 -0
  22. package/resources/ai/templates/examples/rules/functions-bad.ts +12 -0
  23. package/resources/ai/templates/examples/rules/functions-good.ts +12 -0
  24. package/resources/ai/templates/examples/rules/returns-bad.ts +12 -0
  25. package/resources/ai/templates/examples/rules/returns-good.ts +12 -0
  26. package/resources/ai/templates/examples/rules/testing-bad.ts +12 -0
  27. package/resources/ai/templates/examples/rules/testing-good.ts +12 -0
  28. package/resources/ai/templates/rules/architecture.md +2 -0
  29. package/resources/ai/templates/rules/class-first.md +2 -0
  30. package/resources/ai/templates/rules/functions.md +1 -0
  31. package/standards/architecture.md +2 -1
  32. package/standards/manifest.json +1 -1
  33. package/standards/schema.json +2 -11
  34. package/standards/style.md +12 -9
  35. package/templates/node-lib/src/config.ts +1 -0
  36. package/templates/node-lib/src/index.ts +3 -1
  37. package/templates/node-service/src/config.ts +3 -0
  38. package/templates/node-service/src/index.ts +4 -3
  39. 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,6 +110,7 @@ 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
 
@@ -120,6 +124,7 @@ Generated project code is TypeScript-only: implementation and tests live in `.ts
120
124
 
121
125
  Demo structure generated by default:
122
126
 
127
+ - `ai/examples/demo/src/config.ts`
123
128
  - `ai/examples/demo/src/invoices/invoice-service.ts`
124
129
  - `ai/examples/demo/src/invoices/invoice-errors.ts`
125
130
  - `ai/examples/demo/src/invoices/invoice-types.ts`
@@ -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") {
@@ -397,9 +393,7 @@ async function ensureTargetReady(targetPath, force) {
397
393
  const nonGitEntries = entries.filter((entry) => entry !== ".git");
398
394
 
399
395
  if (nonGitEntries.length > 0 && !force) {
400
- throw new Error(
401
- `Target directory is not empty: ${targetPath}. Use --force to continue and overwrite files.`
402
- );
396
+ throw new Error(`Target directory is not empty: ${targetPath}. Use --force to continue and overwrite files.`);
403
397
  }
404
398
  }
405
399
 
@@ -456,9 +450,7 @@ function validateProfile(profile, schema, sourceLabel) {
456
450
  return;
457
451
  }
458
452
 
459
- const details = (validate.errors ?? [])
460
- .map((issue) => `${issue.instancePath || "/"}: ${issue.message ?? "invalid"}`)
461
- .join("; ");
453
+ const details = (validate.errors ?? []).map((issue) => `${issue.instancePath || "/"}: ${issue.message ?? "invalid"}`).join("; ");
462
454
 
463
455
  throw new Error(`Invalid profile at ${sourceLabel}: ${details}`);
464
456
  }
@@ -550,12 +542,7 @@ async function createProfileInteractively(baseProfile) {
550
542
 
551
543
  try {
552
544
  for (const question of PROFILE_QUESTIONS) {
553
- profile[question.key] = await askChoice(
554
- rl,
555
- question.prompt,
556
- question.options,
557
- profile[question.key]
558
- );
545
+ profile[question.key] = await askChoice(rl, question.prompt, question.options, profile[question.key]);
559
546
  }
560
547
  } finally {
561
548
  rl.close();
@@ -573,9 +560,7 @@ async function runProfile(rawOptions) {
573
560
  const packageRoot = resolvePackageRoot();
574
561
  const schema = await loadProfileSchema(packageRoot);
575
562
  const defaultProfilePath = getBundledProfilePath(packageRoot);
576
- const outputPath = rawOptions.profilePath
577
- ? path.resolve(process.cwd(), rawOptions.profilePath)
578
- : defaultProfilePath;
563
+ const outputPath = rawOptions.profilePath ? path.resolve(process.cwd(), rawOptions.profilePath) : defaultProfilePath;
579
564
  const shouldUseNonInteractive = rawOptions.nonInteractive || !process.stdin.isTTY;
580
565
  const exists = await pathExists(outputPath);
581
566
 
@@ -590,11 +575,7 @@ async function runProfile(rawOptions) {
590
575
  });
591
576
 
592
577
  try {
593
- const shouldOverwrite = await promptYesNo(
594
- rl,
595
- `Profile already exists at ${outputPath}. Overwrite?`,
596
- false
597
- );
578
+ const shouldOverwrite = await promptYesNo(rl, `Profile already exists at ${outputPath}. Overwrite?`, false);
598
579
 
599
580
  if (!shouldOverwrite) {
600
581
  console.log("Profile update cancelled.");
@@ -656,44 +637,30 @@ function buildAlternativeRules(profile) {
656
637
 
657
638
  if (profile.return_policy !== "single_return_strict_no_exceptions") {
658
639
  codeRules.push(
659
- "### Return Policy Override (MUST)\n\n" +
660
- `- 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.`
661
641
  );
662
642
  }
663
643
 
664
644
  if (profile.function_size_policy !== "max_30_lines_soft") {
665
- codeRules.push(
666
- "### Function Size Override (MUST)\n\n" +
667
- `- The active function-size policy is \`${profile.function_size_policy}\` and MUST be enforced.`
668
- );
645
+ codeRules.push("### Function Size Override (MUST)\n\n" + `- The active function-size policy is \`${profile.function_size_policy}\` and MUST be enforced.`);
669
646
  }
670
647
 
671
648
  if (profile.error_handling !== "exceptions_with_typed_errors") {
672
- codeRules.push(
673
- "### Error Handling Override (MUST)\n\n" +
674
- `- The active error-handling policy is \`${profile.error_handling}\`.`
675
- );
649
+ codeRules.push("### Error Handling Override (MUST)\n\n" + `- The active error-handling policy is \`${profile.error_handling}\`.`);
676
650
  }
677
651
 
678
652
  if (profile.async_style !== "async_await_only") {
679
- codeRules.push(
680
- "### Async Style Override (MUST)\n\n" +
681
- `- The active async policy is \`${profile.async_style}\` and MUST be followed in new code.`
682
- );
653
+ codeRules.push("### Async Style Override (MUST)\n\n" + `- The active async policy is \`${profile.async_style}\` and MUST be followed in new code.`);
683
654
  }
684
655
 
685
656
  let architectureRule = "";
686
657
  if (profile.architecture !== "feature_folders") {
687
- architectureRule =
688
- "### Architecture Override (MUST)\n\n" +
689
- `- 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.`;
690
659
  }
691
660
 
692
661
  let testingRule = "";
693
662
  if (profile.testing_policy !== "tests_required_for_behavior_change") {
694
- testingRule =
695
- "### Testing Override (MUST)\n\n" +
696
- `- The active testing policy is \`${profile.testing_policy}\`.`;
663
+ testingRule = "### Testing Override (MUST)\n\n" + `- The active testing policy is \`${profile.testing_policy}\`.`;
697
664
  }
698
665
 
699
666
  return {
@@ -722,14 +689,7 @@ async function buildRuleSections(packageRoot, profile) {
722
689
  const readmeRule = await readRule("readme.md");
723
690
 
724
691
  const alternatives = buildAlternativeRules(profile);
725
- const codeGenerationRules = [
726
- classRule,
727
- functionRule,
728
- returnRule,
729
- controlFlowRule,
730
- errorRule,
731
- asyncRule
732
- ];
692
+ const codeGenerationRules = [classRule, functionRule, returnRule, controlFlowRule, errorRule, asyncRule];
733
693
 
734
694
  if (alternatives.codeRules.length > 0) {
735
695
  codeGenerationRules.push(...alternatives.codeRules);
@@ -743,13 +703,7 @@ async function buildRuleSections(packageRoot, profile) {
743
703
  }
744
704
 
745
705
  async function renderProjectAgents(packageRoot, targetDir, projectName, profile) {
746
- const templatePath = path.join(
747
- packageRoot,
748
- "resources",
749
- "ai",
750
- "templates",
751
- "agents.project.template.md"
752
- );
706
+ const templatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
753
707
  const template = await readFile(templatePath, "utf8");
754
708
  const sections = await buildRuleSections(packageRoot, profile);
755
709
 
@@ -807,11 +761,7 @@ async function maybeInitializeProfileInteractively(packageRoot, profilePath) {
807
761
  });
808
762
 
809
763
  try {
810
- const shouldInit = await promptYesNo(
811
- rl,
812
- `Profile not found at ${profilePath}. Initialize it with package defaults?`,
813
- true
814
- );
764
+ const shouldInit = await promptYesNo(rl, `Profile not found at ${profilePath}. Initialize it with package defaults?`, true);
815
765
 
816
766
  if (!shouldInit) {
817
767
  throw new Error("Profile initialization declined by user.");
@@ -874,9 +824,7 @@ async function promptForMissing(options) {
874
824
  const resolved = { ...options };
875
825
 
876
826
  if (!resolved.template) {
877
- const templateAnswer = await rl.question(
878
- "Choose template (node-lib/node-service) [node-lib]: "
879
- );
827
+ const templateAnswer = await rl.question("Choose template (node-lib/node-service) [node-lib]: ");
880
828
  const normalized = templateAnswer.trim() || "node-lib";
881
829
 
882
830
  if (!TEMPLATE_NAMES.includes(normalized)) {
@@ -900,13 +848,7 @@ async function promptForMissing(options) {
900
848
 
901
849
  async function validateInitResources(packageRoot, templateName) {
902
850
  const templateDir = path.join(packageRoot, "templates", templateName);
903
- const agentsTemplatePath = path.join(
904
- packageRoot,
905
- "resources",
906
- "ai",
907
- "templates",
908
- "agents.project.template.md"
909
- );
851
+ const agentsTemplatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
910
852
  const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
911
853
  const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
912
854
 
@@ -944,8 +886,7 @@ async function runInit(rawOptions) {
944
886
 
945
887
  const targetPath = path.resolve(process.cwd());
946
888
  const inferredProjectName = path.basename(targetPath);
947
- const projectName =
948
- inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
889
+ const projectName = inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
949
890
  const packageName = sanitizePackageName(projectName);
950
891
 
951
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sha3/code-standards",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI-first code standards, tooling exports, and project initializer",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- printWidth: 100,
2
+ printWidth: 160,
3
3
  tabWidth: 2,
4
4
  useTabs: false,
5
5
  semi: true,
@@ -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
  ],
@@ -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": 13,
91
- "maxItems": 13,
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
- const CURRENCY_SYMBOL = "$";
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 = `${CURRENCY_SYMBOL}${amount.toFixed(2)}`;
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
  }
@@ -0,0 +1,3 @@
1
+ export const MINIMUM_INVOICE_AMOUNT = 0;
2
+ export const BILLING_CURRENCY_SYMBOL = "$";
3
+ export const STATUS_SERVICE_URL = "https://status.example.com/health";
@@ -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
- const MINIMUM_INVOICE_AMOUNT = 0;
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.
@@ -6,6 +6,7 @@
6
6
  - Types MUST be preferred over interfaces for local modeling unless a public contract requires an interface.
7
7
  - New implementation and test files MUST be `.ts`.
8
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.
9
10
 
10
11
  Good example:
11
12
 
@@ -14,6 +14,7 @@ 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
19
  - Root tooling config files (`eslint`, `prettier`, `tsconfig`) at project root.
19
20
 
@@ -21,4 +22,4 @@ Both templates SHOULD keep this baseline structure:
21
22
 
22
23
  - Keep feature modules cohesive.
23
24
  - Avoid cyclic dependencies.
24
- - Keep configuration centralized at project root.
25
+ - Keep hardcoded application configuration centralized in `src/config.ts`.
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "formatting": {
14
14
  "prettier": true,
15
- "lineLength": 100,
15
+ "lineLength": 160,
16
16
  "semi": true,
17
17
  "singleQuote": false
18
18
  },
@@ -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": 140 },
36
+ "lineLength": { "type": "integer", "minimum": 80, "maximum": 200 },
46
37
  "semi": { "type": "boolean" },
47
38
  "singleQuote": { "type": "boolean" }
48
39
  }
@@ -5,7 +5,8 @@ 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 100.
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
 
@@ -42,14 +43,16 @@ Required block names:
42
43
  3. `consts`
43
44
  4. `types`
44
45
  5. `private:attributes`
45
- 6. `private:properties`
46
- 7. `public:properties`
47
- 8. `constructor`
48
- 9. `static:properties`
49
- 10. `factory`
50
- 11. `private:methods`
51
- 12. `public:methods`
52
- 13. `static:methods`
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`
53
56
 
54
57
  All section blocks MUST be present even when empty. Empty sections MUST include:
55
58
 
@@ -0,0 +1 @@
1
+ export const GREETING_PREFIX = "Hello";
@@ -1,3 +1,5 @@
1
+ import { GREETING_PREFIX } from "./config.js";
2
+
1
3
  export function greet(name: string): string {
2
- return `Hello, ${name}`;
4
+ return `${GREETING_PREFIX}, ${name}`;
3
5
  }
@@ -0,0 +1,3 @@
1
+ export const RESPONSE_CONTENT_TYPE = "application/json";
2
+ export const DEFAULT_PORT = 3000;
3
+ export const EXTERNAL_STATUS_URL = "https://status.example.com/health";
@@ -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", "application/json");
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 ?? "3000");
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) => {