@sha3/code-standards 0.1.1 → 0.1.2
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 +47 -9
- package/bin/code-standards.mjs +30 -36
- package/package.json +1 -1
- package/resources/ai/templates/examples/demo/src/billing/billing-service.ts +102 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-errors.ts +89 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-service.ts +123 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-types.ts +20 -0
- package/resources/ai/templates/examples/rules/async-bad.ts +94 -0
- package/resources/ai/templates/examples/rules/async-good.ts +94 -0
- package/resources/ai/templates/examples/rules/class-first-bad.ts +90 -0
- package/resources/ai/templates/examples/rules/class-first-good.ts +99 -0
- package/resources/ai/templates/examples/rules/constructor-bad.ts +98 -0
- package/resources/ai/templates/examples/rules/constructor-good.ts +97 -0
- package/resources/ai/templates/examples/rules/control-flow-bad.ts +85 -0
- package/resources/ai/templates/examples/rules/control-flow-good.ts +92 -0
- package/resources/ai/templates/examples/rules/errors-bad.ts +86 -0
- package/resources/ai/templates/examples/rules/errors-good.ts +89 -0
- package/resources/ai/templates/examples/rules/functions-bad.ts +106 -0
- package/resources/ai/templates/examples/rules/functions-good.ts +102 -0
- package/resources/ai/templates/examples/rules/returns-bad.ts +92 -0
- package/resources/ai/templates/examples/rules/returns-good.ts +94 -0
- package/resources/ai/templates/examples/rules/testing-bad.ts +88 -0
- package/resources/ai/templates/examples/rules/testing-good.ts +92 -0
- package/resources/ai/templates/rules/architecture.md +5 -15
- package/resources/ai/templates/rules/async.md +2 -12
- package/resources/ai/templates/rules/class-first.md +23 -82
- package/resources/ai/templates/rules/control-flow.md +2 -8
- package/resources/ai/templates/rules/errors.md +2 -11
- package/resources/ai/templates/rules/functions.md +2 -15
- package/resources/ai/templates/rules/returns.md +2 -22
- package/resources/ai/templates/rules/testing.md +2 -14
- package/standards/style.md +9 -1
- package/templates/node-lib/gitignore +5 -0
- package/templates/node-service/gitignore +5 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section imports:externals
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// empty
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section imports:internals
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// empty
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @section consts
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// empty
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @section types
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
type Invoice = { issuedAt: Date };
|
|
24
|
+
|
|
25
|
+
export class InvoiceEscalationPolicy {
|
|
26
|
+
/**
|
|
27
|
+
* @section private:attributes
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// empty
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @section private:properties
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @section public:properties
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// empty
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @section constructor
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
// empty
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @section static:properties
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
// empty
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @section factory
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
public static create(): InvoiceEscalationPolicy {
|
|
61
|
+
const policy = new InvoiceEscalationPolicy();
|
|
62
|
+
return policy;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @section private:methods
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
// empty
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @section public:methods
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
public evaluateEscalation(invoice: Invoice, now: Date): Promise<string> {
|
|
76
|
+
return Promise.resolve(invoice).then((current: Invoice) => {
|
|
77
|
+
const hasAge = now.getTime() - current.issuedAt.getTime() > 0;
|
|
78
|
+
const decision = hasAge ? "x" : "y";
|
|
79
|
+
return decision;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @section static:methods
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
// empty
|
|
88
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section imports:externals
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// empty
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section imports:internals
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// empty
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @section consts
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @section types
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
type Invoice = { issuedAt: Date };
|
|
24
|
+
type EscalationDecision = "manual-review" | "no-escalation";
|
|
25
|
+
|
|
26
|
+
export class InvoiceEscalationPolicy {
|
|
27
|
+
/**
|
|
28
|
+
* @section private:attributes
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// empty
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @section private:properties
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @section public:properties
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// empty
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @section constructor
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
// empty
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @section static:properties
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
// empty
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @section factory
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
public static create(): InvoiceEscalationPolicy {
|
|
62
|
+
const policy = new InvoiceEscalationPolicy();
|
|
63
|
+
return policy;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @section private:methods
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
private daysBetween(from: Date, to: Date): number {
|
|
71
|
+
const diffInMilliseconds = to.getTime() - from.getTime();
|
|
72
|
+
const dayCount = Math.floor(diffInMilliseconds / MILLISECONDS_PER_DAY);
|
|
73
|
+
return dayCount;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @section public:methods
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
// Business rule: invoices older than 30 days are escalated for manual review.
|
|
81
|
+
public evaluateEscalation(invoice: Invoice, now: Date): EscalationDecision {
|
|
82
|
+
const ageInDays: number = this.daysBetween(invoice.issuedAt, now);
|
|
83
|
+
const decision: EscalationDecision = ageInDays > 30 ? "manual-review" : "no-escalation";
|
|
84
|
+
return decision;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @section static:methods
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
// empty
|
|
92
|
+
}
|
|
@@ -6,21 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
Good example:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
src/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
invoice-repository.ts
|
|
14
|
-
invoice-types.ts
|
|
15
|
-
billing/
|
|
16
|
-
billing-service.ts
|
|
17
|
-
```
|
|
9
|
+
- `ai/examples/demo/src/invoices/invoice-service.ts`
|
|
10
|
+
- `ai/examples/demo/src/invoices/invoice-errors.ts`
|
|
11
|
+
- `ai/examples/demo/src/invoices/invoice-types.ts`
|
|
12
|
+
- `ai/examples/demo/src/billing/billing-service.ts`
|
|
18
13
|
|
|
19
14
|
Bad example:
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
src/
|
|
23
|
-
services/
|
|
24
|
-
repositories/
|
|
25
|
-
models/
|
|
26
|
-
```
|
|
16
|
+
- `ai/examples/rules/class-first-bad.ts` (mixes concerns and does not keep feature boundaries)
|
|
@@ -6,18 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
Good example:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
public async execute(command: SyncInvoicesCommand): Promise<SyncResult> {
|
|
11
|
-
const invoices: Invoice[] = await this.source.fetch(command.accountId);
|
|
12
|
-
const result: SyncResult = await this.writer.persist(invoices);
|
|
13
|
-
return result;
|
|
14
|
-
}
|
|
15
|
-
```
|
|
9
|
+
- `ai/examples/rules/async-good.ts`
|
|
16
10
|
|
|
17
11
|
Bad example:
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
public execute(command: SyncInvoicesCommand): Promise<SyncResult> {
|
|
21
|
-
return this.source.fetch(command.accountId).then((invoices) => this.writer.persist(invoices));
|
|
22
|
-
}
|
|
23
|
-
```
|
|
13
|
+
- `ai/examples/rules/async-bad.ts`
|
|
@@ -4,93 +4,34 @@
|
|
|
4
4
|
- Each class MUST use constructor injection for dependencies.
|
|
5
5
|
- A source file MUST expose one public class.
|
|
6
6
|
- Mutable shared state MUST be avoided; prefer `readonly` fields and deterministic methods.
|
|
7
|
-
- Class-oriented files MUST always include all section comment blocks using this exact format:
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
7
|
+
- Class-oriented files MUST always include all section comment blocks using this exact 3-line format:
|
|
8
|
+
- `/**`
|
|
9
|
+
- ` * @section <block-name>`
|
|
10
|
+
- ` */`
|
|
11
|
+
- Required section block names (in order):
|
|
12
|
+
- `imports:externals`
|
|
13
|
+
- `imports:internals`
|
|
14
|
+
- `consts`
|
|
15
|
+
- `types`
|
|
16
|
+
- `private:attributes`
|
|
17
|
+
- `private:properties`
|
|
18
|
+
- `public:properties`
|
|
19
|
+
- `constructor`
|
|
20
|
+
- `static:properties`
|
|
21
|
+
- `factory`
|
|
22
|
+
- `private:methods`
|
|
23
|
+
- `public:methods`
|
|
24
|
+
- `static:methods`
|
|
21
25
|
- All section blocks MUST exist even when empty, using `// empty` after the section marker.
|
|
22
26
|
- `factory` MUST only contain methods that create and return instances of the same class.
|
|
23
27
|
|
|
24
28
|
Good example:
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/** @section imports:internals */
|
|
31
|
-
import type { InvoiceRepository } from "./invoice-repository.js";
|
|
32
|
-
import type { CreateInvoiceCommand, Invoice } from "./invoice-types.js";
|
|
33
|
-
|
|
34
|
-
/** @section consts */
|
|
35
|
-
const SERVICE_NAME = "invoice-service";
|
|
36
|
-
|
|
37
|
-
/** @section types */
|
|
38
|
-
type InvoiceDraft = { customerId: string; amount: number };
|
|
39
|
-
|
|
40
|
-
export class InvoiceService {
|
|
41
|
-
/** @section private:attributes */
|
|
42
|
-
private readonly requestId: string;
|
|
43
|
-
|
|
44
|
-
/** @section private:properties */
|
|
45
|
-
private readonly repository: InvoiceRepository;
|
|
46
|
-
|
|
47
|
-
/** @section public:properties */
|
|
48
|
-
public readonly serviceName: string;
|
|
49
|
-
|
|
50
|
-
/** @section constructor */
|
|
51
|
-
public constructor(repository: InvoiceRepository) {
|
|
52
|
-
this.repository = repository;
|
|
53
|
-
this.requestId = randomUUID();
|
|
54
|
-
this.serviceName = SERVICE_NAME;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @section static:properties */
|
|
58
|
-
// empty
|
|
59
|
-
|
|
60
|
-
/** @section factory */
|
|
61
|
-
public static create(repository: InvoiceRepository): InvoiceService {
|
|
62
|
-
const service = new InvoiceService(repository);
|
|
63
|
-
return service;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** @section private:methods */
|
|
67
|
-
private toInvoiceDraft(command: CreateInvoiceCommand): InvoiceDraft {
|
|
68
|
-
const draft: InvoiceDraft = { customerId: command.customerId, amount: command.amount };
|
|
69
|
-
return draft;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** @section public:methods */
|
|
73
|
-
public async create(command: CreateInvoiceCommand): Promise<Invoice> {
|
|
74
|
-
const draft: InvoiceDraft = this.toInvoiceDraft(command);
|
|
75
|
-
const invoice: Invoice = await this.repository.save(draft);
|
|
76
|
-
return invoice;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** @section static:methods */
|
|
80
|
-
// empty
|
|
81
|
-
}
|
|
82
|
-
```
|
|
30
|
+
- `ai/examples/rules/class-first-good.ts`
|
|
31
|
+
- `ai/examples/rules/constructor-good.ts`
|
|
32
|
+
- `ai/examples/demo/src/invoices/invoice-service.ts`
|
|
83
33
|
|
|
84
34
|
Bad example:
|
|
85
35
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
export class InvoiceService {
|
|
90
|
-
public async create(command: CreateInvoiceCommand): Promise<Invoice> {
|
|
91
|
-
const repository = new InvoiceRepository();
|
|
92
|
-
if (!command.customerId) return Promise.reject(new Error("invalid"));
|
|
93
|
-
return repository.save(command as any);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
```
|
|
36
|
+
- `ai/examples/rules/class-first-bad.ts`
|
|
37
|
+
- `ai/examples/rules/constructor-bad.ts`
|
|
@@ -6,17 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
Good example:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
export class InvoiceNotFoundError extends Error {
|
|
11
|
-
public constructor(invoiceId: string) {
|
|
12
|
-
super(`Invoice not found: ${invoiceId}`);
|
|
13
|
-
this.name = "InvoiceNotFoundError";
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
```
|
|
9
|
+
- `ai/examples/rules/errors-good.ts`
|
|
17
10
|
|
|
18
11
|
Bad example:
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
throw new Error("Oops");
|
|
22
|
-
```
|
|
13
|
+
- `ai/examples/rules/errors-bad.ts`
|
|
@@ -7,21 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
Good example:
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
private normalize(input: PaymentInput): PaymentDraft {
|
|
12
|
-
const amount: number = normalizeAmount(input.amount);
|
|
13
|
-
const currency: CurrencyCode = normalizeCurrency(input.currency);
|
|
14
|
-
const metadata: PaymentMetadata = sanitizeMetadata(input.metadata);
|
|
15
|
-
return { amount, currency, metadata };
|
|
16
|
-
}
|
|
17
|
-
```
|
|
10
|
+
- `ai/examples/rules/functions-good.ts`
|
|
18
11
|
|
|
19
12
|
Bad example:
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
private normalize(input: any): any {
|
|
23
|
-
// huge branchy function with parsing, IO, validation and persistence mixed together
|
|
24
|
-
// ... 80+ lines omitted
|
|
25
|
-
return input;
|
|
26
|
-
}
|
|
27
|
-
```
|
|
14
|
+
- `ai/examples/rules/functions-bad.ts`
|
|
@@ -6,28 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
Good example:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
public toStatusLabel(status: InvoiceStatus): string {
|
|
11
|
-
let label: string;
|
|
12
|
-
|
|
13
|
-
if (status === "paid") {
|
|
14
|
-
label = "Paid";
|
|
15
|
-
} else if (status === "void") {
|
|
16
|
-
label = "Void";
|
|
17
|
-
} else {
|
|
18
|
-
label = "Pending";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return label;
|
|
22
|
-
}
|
|
23
|
-
```
|
|
9
|
+
- `ai/examples/rules/returns-good.ts`
|
|
24
10
|
|
|
25
11
|
Bad example:
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
public toStatusLabel(status: InvoiceStatus): string {
|
|
29
|
-
if (status === "paid") return "Paid";
|
|
30
|
-
if (status === "void") return "Void";
|
|
31
|
-
return "Pending";
|
|
32
|
-
}
|
|
33
|
-
```
|
|
13
|
+
- `ai/examples/rules/returns-bad.ts`
|
|
@@ -7,20 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
Good example:
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
// Business rule: invoices older than 30 days are escalated for manual review.
|
|
12
|
-
public evaluateEscalation(invoice: Invoice, now: Date): EscalationDecision {
|
|
13
|
-
const ageInDays: number = daysBetween(invoice.issuedAt, now);
|
|
14
|
-
const decision: EscalationDecision =
|
|
15
|
-
ageInDays > 30 ? "manual-review" : "no-escalation";
|
|
16
|
-
return decision;
|
|
17
|
-
}
|
|
18
|
-
```
|
|
10
|
+
- `ai/examples/rules/testing-good.ts`
|
|
19
11
|
|
|
20
12
|
Bad example:
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
public evaluateEscalation(invoice: any, now: Date): any {
|
|
24
|
-
return Promise.resolve(invoice).then((it) => (Date.now() - it.issuedAt > 0 ? "x" : "y"));
|
|
25
|
-
}
|
|
26
|
-
```
|
|
14
|
+
- `ai/examples/rules/testing-bad.ts`
|
package/standards/style.md
CHANGED
|
@@ -25,7 +25,15 @@ All code MUST follow the canonical rules in `standards/manifest.json`.
|
|
|
25
25
|
|
|
26
26
|
## Class File Comment Blocks
|
|
27
27
|
|
|
28
|
-
Class-oriented files MUST use
|
|
28
|
+
Class-oriented files MUST use 3-line JSDoc section markers in this exact order:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
/**
|
|
32
|
+
* @section <block-name>
|
|
33
|
+
*/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Required block names:
|
|
29
37
|
|
|
30
38
|
1. `imports:externals`
|
|
31
39
|
2. `imports:internals`
|