@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.
Files changed (41) hide show
  1. package/README.md +8 -0
  2. package/bin/code-standards.mjs +23 -81
  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 +3 -0
  31. package/resources/ai/templates/rules/testing.md +1 -0
  32. package/standards/architecture.md +3 -2
  33. package/standards/manifest.json +1 -1
  34. package/standards/schema.json +2 -11
  35. package/standards/style.md +14 -9
  36. package/standards/testing.md +1 -1
  37. package/templates/node-lib/src/config.ts +1 -0
  38. package/templates/node-lib/src/index.ts +3 -1
  39. package/templates/node-service/src/config.ts +3 -0
  40. package/templates/node-service/src/index.ts +4 -3
  41. 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`
@@ -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 (entries.length > 0 && !force) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sha3/code-standards",
3
- "version": "0.1.2",
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.
@@ -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.config.mjs`, `prettier.config.cjs`, and `tsconfig.json` at root.
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 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,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 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
 
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. `private:properties`
44
- 7. `public:properties`
45
- 8. `constructor`
46
- 9. `static:properties`
47
- 10. `factory`
48
- 11. `private:methods`
49
- 12. `public:methods`
50
- 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`
51
56
 
52
57
  All section blocks MUST be present even when empty. Empty sections MUST include:
53
58
 
@@ -5,7 +5,7 @@ Projects MUST use the Node test runner.
5
5
  ## Location
6
6
 
7
7
  - Store tests under `test/`.
8
- - Use `*.test.ts` or `*.test.js` naming.
8
+ - Use `*.test.ts` naming only.
9
9
 
10
10
  ## Commands
11
11
 
@@ -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) => {