@sha3/code 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +75 -0
- package/README.md +554 -0
- package/ai/adapters/codex.md +7 -0
- package/ai/adapters/copilot.md +7 -0
- package/ai/adapters/cursor.md +7 -0
- package/ai/adapters/windsurf.md +8 -0
- package/ai/constitution.md +12 -0
- package/bin/code-standards.mjs +47 -0
- package/biome.json +37 -0
- package/index.mjs +11 -0
- package/lib/cli/parse-args.mjs +416 -0
- package/lib/cli/post-run-guidance.mjs +43 -0
- package/lib/cli/run-init.mjs +123 -0
- package/lib/cli/run-profile.mjs +46 -0
- package/lib/cli/run-refactor.mjs +152 -0
- package/lib/cli/run-verify.mjs +67 -0
- package/lib/constants.mjs +167 -0
- package/lib/contract/load-rule-catalog.mjs +12 -0
- package/lib/contract/render-agents.mjs +79 -0
- package/lib/contract/render-contract-json.mjs +7 -0
- package/lib/contract/resolve-contract.mjs +52 -0
- package/lib/paths.mjs +50 -0
- package/lib/profile.mjs +108 -0
- package/lib/project/ai-instructions.mjs +28 -0
- package/lib/project/biome-ignore.mjs +14 -0
- package/lib/project/managed-files.mjs +105 -0
- package/lib/project/package-metadata.mjs +132 -0
- package/lib/project/prompt-files.mjs +111 -0
- package/lib/project/template-resolution.mjs +70 -0
- package/lib/refactor/materialize-refactor-context.mjs +106 -0
- package/lib/refactor/preservation-questions.mjs +33 -0
- package/lib/refactor/public-contract-extractor.mjs +22 -0
- package/lib/refactor/render-analysis-summary.mjs +50 -0
- package/lib/refactor/source-analysis.mjs +74 -0
- package/lib/utils/fs.mjs +220 -0
- package/lib/utils/prompts.mjs +63 -0
- package/lib/utils/text.mjs +43 -0
- package/lib/verify/change-audit-verifier.mjs +140 -0
- package/lib/verify/change-context.mjs +36 -0
- package/lib/verify/error-handling-verifier.mjs +164 -0
- package/lib/verify/explain-rule.mjs +54 -0
- package/lib/verify/issue-helpers.mjs +132 -0
- package/lib/verify/project-layout-verifier.mjs +259 -0
- package/lib/verify/project-verifier.mjs +267 -0
- package/lib/verify/readme-public-api.mjs +237 -0
- package/lib/verify/readme-verifier.mjs +216 -0
- package/lib/verify/render-json-report.mjs +3 -0
- package/lib/verify/render-text-report.mjs +34 -0
- package/lib/verify/source-analysis.mjs +126 -0
- package/lib/verify/source-rule-verifier.mjs +453 -0
- package/lib/verify/testing-verifier.mjs +113 -0
- package/lib/verify/tooling-verifier.mjs +82 -0
- package/lib/verify/typescript-style-verifier.mjs +407 -0
- package/package.json +55 -0
- package/profiles/default.profile.json +40 -0
- package/profiles/schema.json +96 -0
- package/prompts/init-contract.md +25 -0
- package/prompts/init-phase-2-implement.md +25 -0
- package/prompts/init-phase-3-verify.md +23 -0
- package/prompts/init.prompt.md +24 -0
- package/prompts/refactor-contract.md +26 -0
- package/prompts/refactor-phase-2-rebuild.md +25 -0
- package/prompts/refactor-phase-3-verify.md +24 -0
- package/prompts/refactor.prompt.md +26 -0
- package/resources/ai/AGENTS.md +18 -0
- package/resources/ai/adapters/codex.md +5 -0
- package/resources/ai/adapters/copilot.md +5 -0
- package/resources/ai/adapters/cursor.md +5 -0
- package/resources/ai/adapters/windsurf.md +5 -0
- package/resources/ai/contract.schema.json +68 -0
- package/resources/ai/rule-catalog.json +878 -0
- package/resources/ai/rule-catalog.schema.json +66 -0
- package/resources/ai/templates/adapters/codex.template.md +7 -0
- package/resources/ai/templates/adapters/copilot.template.md +7 -0
- package/resources/ai/templates/adapters/cursor.template.md +7 -0
- package/resources/ai/templates/adapters/windsurf.template.md +7 -0
- package/resources/ai/templates/agents.project.template.md +141 -0
- package/resources/ai/templates/examples/demo/src/billing/billing.service.ts +73 -0
- package/resources/ai/templates/examples/demo/src/config.ts +3 -0
- package/resources/ai/templates/examples/demo/src/invoice/invoice.errors.ts +51 -0
- package/resources/ai/templates/examples/demo/src/invoice/invoice.service.ts +96 -0
- package/resources/ai/templates/examples/demo/src/invoice/invoice.types.ts +9 -0
- package/resources/ai/templates/examples/rules/async-bad.ts +52 -0
- package/resources/ai/templates/examples/rules/async-good.ts +56 -0
- package/resources/ai/templates/examples/rules/class-first-bad.ts +36 -0
- package/resources/ai/templates/examples/rules/class-first-good.ts +74 -0
- package/resources/ai/templates/examples/rules/constructor-bad.ts +68 -0
- package/resources/ai/templates/examples/rules/constructor-good.ts +71 -0
- package/resources/ai/templates/examples/rules/control-flow-bad.ts +31 -0
- package/resources/ai/templates/examples/rules/control-flow-good.ts +54 -0
- package/resources/ai/templates/examples/rules/errors-bad.ts +42 -0
- package/resources/ai/templates/examples/rules/errors-good.ts +23 -0
- package/resources/ai/templates/examples/rules/functions-bad.ts +48 -0
- package/resources/ai/templates/examples/rules/functions-good.ts +58 -0
- package/resources/ai/templates/examples/rules/returns-bad.ts +38 -0
- package/resources/ai/templates/examples/rules/returns-good.ts +44 -0
- package/resources/ai/templates/examples/rules/testing-bad.ts +34 -0
- package/resources/ai/templates/examples/rules/testing-good.ts +54 -0
- package/resources/ai/templates/rules/architecture.md +41 -0
- package/resources/ai/templates/rules/async.md +13 -0
- package/resources/ai/templates/rules/class-first.md +45 -0
- package/resources/ai/templates/rules/control-flow.md +13 -0
- package/resources/ai/templates/rules/errors.md +18 -0
- package/resources/ai/templates/rules/functions.md +29 -0
- package/resources/ai/templates/rules/naming.md +13 -0
- package/resources/ai/templates/rules/readme.md +36 -0
- package/resources/ai/templates/rules/returns.md +13 -0
- package/resources/ai/templates/rules/testing.md +18 -0
- package/resources/ai/templates/rules.project.template.md +66 -0
- package/resources/ai/templates/skills/change-synchronization/SKILL.md +42 -0
- package/resources/ai/templates/skills/feature-shaping/SKILL.md +45 -0
- package/resources/ai/templates/skills/http-api-conventions/SKILL.md +171 -0
- package/resources/ai/templates/skills/init-workflow/SKILL.md +52 -0
- package/resources/ai/templates/skills/readme-authoring/SKILL.md +51 -0
- package/resources/ai/templates/skills/refactor-workflow/SKILL.md +50 -0
- package/resources/ai/templates/skills/simplicity-audit/SKILL.md +41 -0
- package/resources/ai/templates/skills/test-scope-selection/SKILL.md +50 -0
- package/resources/ai/templates/skills.index.template.md +25 -0
- package/standards/architecture.md +72 -0
- package/standards/changelog-policy.md +12 -0
- package/standards/manifest.json +36 -0
- package/standards/readme.md +56 -0
- package/standards/schema.json +124 -0
- package/standards/style.md +106 -0
- package/standards/testing.md +20 -0
- package/standards/tooling.md +38 -0
- package/templates/node-lib/.biomeignore +10 -0
- package/templates/node-lib/.vscode/extensions.json +1 -0
- package/templates/node-lib/.vscode/settings.json +9 -0
- package/templates/node-lib/README.md +172 -0
- package/templates/node-lib/biome.json +37 -0
- package/templates/node-lib/gitignore +6 -0
- package/templates/node-lib/package.json +32 -0
- package/templates/node-lib/scripts/release-publish.mjs +106 -0
- package/templates/node-lib/scripts/run-tests.mjs +65 -0
- package/templates/node-lib/src/config.ts +3 -0
- package/templates/node-lib/src/index.ts +2 -0
- package/templates/node-lib/src/logger.ts +7 -0
- package/templates/node-lib/src/package-info/package-info.service.ts +47 -0
- package/templates/node-lib/test/package-info.test.ts +10 -0
- package/templates/node-lib/tsconfig.build.json +1 -0
- package/templates/node-lib/tsconfig.json +5 -0
- package/templates/node-service/.biomeignore +10 -0
- package/templates/node-service/.vscode/extensions.json +1 -0
- package/templates/node-service/.vscode/settings.json +9 -0
- package/templates/node-service/README.md +244 -0
- package/templates/node-service/biome.json +37 -0
- package/templates/node-service/ecosystem.config.cjs +3 -0
- package/templates/node-service/gitignore +6 -0
- package/templates/node-service/package.json +42 -0
- package/templates/node-service/scripts/release-publish.mjs +106 -0
- package/templates/node-service/scripts/run-tests.mjs +65 -0
- package/templates/node-service/src/app/service-runtime.service.ts +57 -0
- package/templates/node-service/src/app-info/app-info.service.ts +47 -0
- package/templates/node-service/src/config.ts +11 -0
- package/templates/node-service/src/http/http-server.service.ts +66 -0
- package/templates/node-service/src/index.ts +2 -0
- package/templates/node-service/src/logger.ts +7 -0
- package/templates/node-service/src/main.ts +5 -0
- package/templates/node-service/test/service-runtime.test.ts +13 -0
- package/templates/node-service/tsconfig.build.json +1 -0
- package/templates/node-service/tsconfig.json +5 -0
- package/tsconfig/base.json +16 -0
- package/tsconfig/node-lib.json +5 -0
- package/tsconfig/node-service.json +1 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section imports:externals
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section consts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const serviceName = "invoice-service";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @section types
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type CreateInvoiceCommand = { customerId: string; amount: number };
|
|
18
|
+
type Invoice = { id: string; customerId: string; amount: number; createdAt: Date };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @section class
|
|
22
|
+
*/
|
|
23
|
+
export class InvoiceService {
|
|
24
|
+
/**
|
|
25
|
+
* @section private:attributes
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
private readonly requestId: string;
|
|
29
|
+
private readonly invoicesById: Map<string, Invoice>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @section public:properties
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
public readonly serviceName: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @section constructor
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
public constructor() {
|
|
42
|
+
this.requestId = randomUUID();
|
|
43
|
+
this.invoicesById = new Map<string, Invoice>();
|
|
44
|
+
this.serviceName = serviceName;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @section factory
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
public static create(): InvoiceService {
|
|
52
|
+
const service = new InvoiceService();
|
|
53
|
+
return service;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @section private:methods
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
private toInvoice(command: CreateInvoiceCommand): Invoice {
|
|
61
|
+
const invoice: Invoice = { id: randomUUID(), customerId: command.customerId, amount: command.amount, createdAt: new Date() };
|
|
62
|
+
return invoice;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @section public:methods
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
public async create(command: CreateInvoiceCommand): Promise<Invoice> {
|
|
70
|
+
const invoice: Invoice = this.toInvoice(command);
|
|
71
|
+
this.invoicesById.set(invoice.id, invoice);
|
|
72
|
+
return invoice;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section consts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fallbackPrefix = "INV";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section types
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type Clock = () => Date;
|
|
12
|
+
type IdFactory = () => string;
|
|
13
|
+
|
|
14
|
+
export class InvoiceIdBuilder {
|
|
15
|
+
/**
|
|
16
|
+
* @section private:attributes
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
private readonly clock: Clock;
|
|
20
|
+
private readonly idFactory: IdFactory;
|
|
21
|
+
private readonly prefix: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @section constructor
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
public constructor() {
|
|
28
|
+
// Bad: constructor wires concrete dependencies and environment details directly.
|
|
29
|
+
this.clock = () => new Date();
|
|
30
|
+
this.idFactory = () => Math.random().toString(36).slice(2, 8);
|
|
31
|
+
this.prefix = process.env.INVOICE_PREFIX || fallbackPrefix;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @section factory
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
public static create(): InvoiceIdBuilder {
|
|
39
|
+
const builder = new InvoiceIdBuilder();
|
|
40
|
+
return builder;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @section private:methods
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
private currentYear(): number {
|
|
48
|
+
const year = this.clock().getUTCFullYear();
|
|
49
|
+
return year;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @section public:methods
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
public build(): string {
|
|
57
|
+
const rawId = this.idFactory();
|
|
58
|
+
const year = this.currentYear();
|
|
59
|
+
const invoiceId = `${this.prefix}-${year}-${rawId}`;
|
|
60
|
+
return invoiceId;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @section static:methods
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
// empty
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section consts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const defaultInvoicePrefix = "INV";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section types
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type Clock = () => Date;
|
|
12
|
+
type IdFactory = () => string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @section class
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export class InvoiceIdBuilder {
|
|
19
|
+
/**
|
|
20
|
+
* @section private:attributes
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
private readonly clock: Clock;
|
|
24
|
+
private readonly idFactory: IdFactory;
|
|
25
|
+
private readonly prefix: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @section constructor
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
public constructor(clock: Clock, idFactory: IdFactory, prefix = defaultInvoicePrefix) {
|
|
32
|
+
this.clock = clock;
|
|
33
|
+
this.idFactory = idFactory;
|
|
34
|
+
this.prefix = prefix;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @section factory
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
public static create(clock: Clock, idFactory: IdFactory): InvoiceIdBuilder {
|
|
42
|
+
const builder = new InvoiceIdBuilder(clock, idFactory);
|
|
43
|
+
return builder;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @section private:methods
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
private currentYear(): number {
|
|
51
|
+
const year = this.clock().getUTCFullYear();
|
|
52
|
+
return year;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @section public:methods
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
public build(): string {
|
|
60
|
+
const rawId = this.idFactory();
|
|
61
|
+
const year = this.currentYear();
|
|
62
|
+
const invoiceId = `${this.prefix}-${year}-${rawId}`;
|
|
63
|
+
return invoiceId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @section static:methods
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
// empty
|
|
71
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// empty
|
|
6
|
+
|
|
7
|
+
export class ControlFlowPolicy {
|
|
8
|
+
/**
|
|
9
|
+
* @section factory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
public static create(): ControlFlowPolicy {
|
|
13
|
+
const policy = new ControlFlowPolicy();
|
|
14
|
+
return policy;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @section public:methods
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
public badSnippet(): string {
|
|
22
|
+
const snippet = "if (isEnabled) executeTask();";
|
|
23
|
+
return snippet;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @section static:methods
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// empty
|
|
31
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type FeatureFlagMap = Record<string, boolean>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section class
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class FeatureGate {
|
|
12
|
+
/**
|
|
13
|
+
* @section private:attributes
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
private readonly flags: FeatureFlagMap;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @section constructor
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
public constructor(flags: FeatureFlagMap) {
|
|
23
|
+
this.flags = flags;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @section factory
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
public static from(flags: FeatureFlagMap): FeatureGate {
|
|
31
|
+
const gate = new FeatureGate(flags);
|
|
32
|
+
return gate;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @section public:methods
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
public canRunTask(key: string): boolean {
|
|
40
|
+
let enabled = false;
|
|
41
|
+
|
|
42
|
+
if (this.flags[key] === true) {
|
|
43
|
+
enabled = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return enabled;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @section static:methods
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// empty
|
|
54
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// empty
|
|
6
|
+
|
|
7
|
+
export class InvoiceLookup {
|
|
8
|
+
/**
|
|
9
|
+
* @section factory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
public static create(): InvoiceLookup {
|
|
13
|
+
const lookup = new InvoiceLookup();
|
|
14
|
+
return lookup;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @section public:methods
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
public ensureInvoiceExists(invoiceId: string, exists: boolean): void {
|
|
22
|
+
try {
|
|
23
|
+
if (!exists) {
|
|
24
|
+
throw "missing";
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// bad: error is silently swallowed
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!exists) {
|
|
31
|
+
throw new Error("failed");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(invoiceId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @section static:methods
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// empty
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section class
|
|
3
|
+
*/
|
|
4
|
+
export class InvoiceLookup {
|
|
5
|
+
/**
|
|
6
|
+
* @section factory
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
public static create(): InvoiceLookup {
|
|
10
|
+
const lookup = new InvoiceLookup();
|
|
11
|
+
return lookup;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @section public:methods
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
public ensureInvoiceExists(invoiceId: string, exists: boolean): void {
|
|
19
|
+
if (!exists) {
|
|
20
|
+
throw new Error(`Invoice not found: invoiceId=${invoiceId}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type PaymentInput = { amount?: number; currency?: string; metadata?: Record<string, string> };
|
|
6
|
+
type PersistedPaymentInput = PaymentInput & { normalizedAt: number; saved: boolean };
|
|
7
|
+
|
|
8
|
+
export class PaymentNormalizer {
|
|
9
|
+
/**
|
|
10
|
+
* @section factory
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
public static create(): PaymentNormalizer {
|
|
14
|
+
const normalizer = new PaymentNormalizer();
|
|
15
|
+
return normalizer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @section public:methods
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
public normalize(input: PaymentInput): PersistedPaymentInput {
|
|
23
|
+
// Bad: too many responsibilities in one method.
|
|
24
|
+
const clone = { ...input };
|
|
25
|
+
|
|
26
|
+
if (!clone.currency) {
|
|
27
|
+
clone.currency = "usd";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof clone.currency === "string") {
|
|
31
|
+
clone.currency = clone.currency.toUpperCase();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!clone.metadata) {
|
|
35
|
+
clone.metadata = {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
clone.amount = Number(clone.amount || 0);
|
|
39
|
+
const persisted: PersistedPaymentInput = { ...clone, normalizedAt: Date.now(), saved: true };
|
|
40
|
+
return persisted;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @section static:methods
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// empty
|
|
48
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type PaymentInput = { amount: number; currency: string; metadata: Record<string, string> };
|
|
6
|
+
type PaymentDraft = { amount: number; currency: string; metadata: Record<string, string> };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @section class
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class PaymentNormalizer {
|
|
13
|
+
/**
|
|
14
|
+
* @section factory
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
public static create(): PaymentNormalizer {
|
|
18
|
+
const normalizer = new PaymentNormalizer();
|
|
19
|
+
return normalizer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @section private:methods
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
private normalizeAmount(amount: number): number {
|
|
27
|
+
const normalizedAmount = Number(amount.toFixed(2));
|
|
28
|
+
return normalizedAmount;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private normalizeCurrency(currency: string): string {
|
|
32
|
+
const normalizedCurrency = currency.trim().toUpperCase();
|
|
33
|
+
return normalizedCurrency;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private sanitizeMetadata(metadata: Record<string, string>): Record<string, string> {
|
|
37
|
+
const sanitizedMetadata: Record<string, string> = { ...metadata };
|
|
38
|
+
return sanitizedMetadata;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @section public:methods
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
public normalize(input: PaymentInput): PaymentDraft {
|
|
46
|
+
const amount: number = this.normalizeAmount(input.amount);
|
|
47
|
+
const currency: string = this.normalizeCurrency(input.currency);
|
|
48
|
+
const metadata: Record<string, string> = this.sanitizeMetadata(input.metadata);
|
|
49
|
+
const draft: PaymentDraft = { amount, currency, metadata };
|
|
50
|
+
return draft;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @section static:methods
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
// empty
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type InvoiceStatus = "paid" | "void" | "pending";
|
|
6
|
+
|
|
7
|
+
export class InvoiceStatusPresenter {
|
|
8
|
+
/**
|
|
9
|
+
* @section factory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
public static create(): InvoiceStatusPresenter {
|
|
13
|
+
const presenter = new InvoiceStatusPresenter();
|
|
14
|
+
return presenter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @section public:methods
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
public toStatusLabel(status: InvoiceStatus): string {
|
|
22
|
+
if (status === "paid") {
|
|
23
|
+
return "Paid";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (status === "void") {
|
|
27
|
+
return "Void";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return "Pending";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @section static:methods
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type InvoiceStatus = "paid" | "void" | "pending";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section class
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class InvoiceStatusPresenter {
|
|
12
|
+
/**
|
|
13
|
+
* @section factory
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
public static create(): InvoiceStatusPresenter {
|
|
17
|
+
const presenter = new InvoiceStatusPresenter();
|
|
18
|
+
return presenter;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @section public:methods
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
public toStatusLabel(status: InvoiceStatus): string {
|
|
26
|
+
let label: string;
|
|
27
|
+
|
|
28
|
+
if (status === "paid") {
|
|
29
|
+
label = "Paid";
|
|
30
|
+
} else if (status === "void") {
|
|
31
|
+
label = "Void";
|
|
32
|
+
} else {
|
|
33
|
+
label = "Pending";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return label;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @section static:methods
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// empty
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
type Invoice = { issuedAt: Date };
|
|
6
|
+
|
|
7
|
+
export class InvoiceEscalationPolicy {
|
|
8
|
+
/**
|
|
9
|
+
* @section factory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
public static create(): InvoiceEscalationPolicy {
|
|
13
|
+
const policy = new InvoiceEscalationPolicy();
|
|
14
|
+
return policy;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @section public:methods
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
public evaluateEscalation(invoice: Invoice, now: Date): Promise<string> {
|
|
22
|
+
return Promise.resolve(invoice).then((current: Invoice) => {
|
|
23
|
+
const hasAge = now.getTime() - current.issuedAt.getTime() > 0;
|
|
24
|
+
const decision = hasAge ? "x" : "y";
|
|
25
|
+
return decision;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @section static:methods
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// empty
|
|
34
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @section consts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const millisecondsPerDay = 24 * 60 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @section types
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type Invoice = { issuedAt: Date };
|
|
12
|
+
type EscalationDecision = "manual-review" | "no-escalation";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @section class
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export class InvoiceEscalationPolicy {
|
|
19
|
+
/**
|
|
20
|
+
* @section factory
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
public static create(): InvoiceEscalationPolicy {
|
|
24
|
+
const policy = new InvoiceEscalationPolicy();
|
|
25
|
+
return policy;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @section private:methods
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
private daysBetween(from: Date, to: Date): number {
|
|
33
|
+
const diffInMilliseconds = to.getTime() - from.getTime();
|
|
34
|
+
const dayCount = Math.floor(diffInMilliseconds / millisecondsPerDay);
|
|
35
|
+
return dayCount;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @section public:methods
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// Business rule: invoices older than 30 days are escalated for manual review.
|
|
43
|
+
public evaluateEscalation(invoice: Invoice, now: Date): EscalationDecision {
|
|
44
|
+
const ageInDays: number = this.daysBetween(invoice.issuedAt, now);
|
|
45
|
+
const decision: EscalationDecision = ageInDays > 30 ? "manual-review" : "no-escalation";
|
|
46
|
+
return decision;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @section static:methods
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// empty
|
|
54
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
### Feature-Folder Architecture (MUST)
|
|
2
|
+
|
|
3
|
+
- Code MUST be organized by feature (for example: `src/user`, `src/billing`, `src/invoice`).
|
|
4
|
+
- Feature folder names MUST be singular.
|
|
5
|
+
- Each feature MUST keep its own domain model, application services, and infrastructure adapters grouped by feature.
|
|
6
|
+
- Cross-feature imports MUST happen through explicit public entry points.
|
|
7
|
+
- `src/` MUST stay feature-first with `src/index.ts` and `src/<feature>/`.
|
|
8
|
+
- `src/app/` MUST exist only when the project has explicit composition/wiring that justifies it.
|
|
9
|
+
- `src/shared/` MUST exist only when cross-feature modules actually exist.
|
|
10
|
+
- Project layout MUST include `src/`, `test/`, `scripts/`, `docs/`, and `ai/` at minimum.
|
|
11
|
+
- For `node-service`, HTTP transport concerns SHOULD live in `src/http/` (`routes`, `controllers`, `middleware`).
|
|
12
|
+
- If a service needs to expose an HTTP API, it MUST use the latest published `hono` release line as the HTTP framework.
|
|
13
|
+
- For `node-lib`, API boundary separation MAY use `src/public/` and `src/internal/`.
|
|
14
|
+
- Feature files SHOULD use explicit role suffixes (`*.service.ts`, `*.repository.ts`, `*.schema.ts`, `*.mapper.ts`).
|
|
15
|
+
- Use `*.types.ts` only when shared feature types are substantial enough to justify a dedicated file.
|
|
16
|
+
- Inside `src/<feature>/`, files MUST expose exactly one public class unless the file is `*.types.ts`.
|
|
17
|
+
- Feature file domain base names SHOULD be singular (for example: `invoice.service.ts`).
|
|
18
|
+
- Ambiguous filenames (`utils.ts`, `helpers.ts`, `common.ts`) are forbidden for new feature code.
|
|
19
|
+
- Hardcoded, non-parameterized configuration MUST be centralized in `src/config.ts` (for example, external service URLs).
|
|
20
|
+
- `src/config.ts` MUST export a single default object named `config` and it MUST always be imported as `import config from ".../config.ts"`.
|
|
21
|
+
- Non-trivial structured output (for example pages, documents, markdown, HTML, prompts, emails, or report bodies) MUST be stored in dedicated template files instead of being composed inline in source code.
|
|
22
|
+
- Inline string building is allowed only for very small fragments where a template file would be disproportionate.
|
|
23
|
+
- Template rendering strategy MAY be as simple or as capable as needed; choose the smallest approach that keeps the content outside application code.
|
|
24
|
+
- Architecture decisions MUST default to the simplest structure that satisfies current requirements.
|
|
25
|
+
- New files, modules, or layers MUST be introduced only when they solve present complexity, not anticipated future needs.
|
|
26
|
+
- Simplicity is mandatory: if a design can be smaller, flatter, and more direct while staying correct, that simpler design MUST be chosen.
|
|
27
|
+
- Speculative abstractions, placeholder extension points, and gratuitous indirection are forbidden.
|
|
28
|
+
- Simplicity MUST NOT be interpreted as permission to remove valid architectural boundaries.
|
|
29
|
+
- Distinct responsibilities that are already justified SHOULD remain separated.
|
|
30
|
+
|
|
31
|
+
Good example:
|
|
32
|
+
|
|
33
|
+
- `ai/examples/demo/src/config.ts`
|
|
34
|
+
- `ai/examples/demo/src/invoice/invoice.service.ts`
|
|
35
|
+
- `ai/examples/demo/src/invoice/invoice.errors.ts`
|
|
36
|
+
- `ai/examples/demo/src/invoice/invoice.types.ts`
|
|
37
|
+
- `ai/examples/demo/src/billing/billing.service.ts`
|
|
38
|
+
|
|
39
|
+
Bad example:
|
|
40
|
+
|
|
41
|
+
- `ai/examples/rules/class-first-bad.ts` (mixes concerns and does not keep feature boundaries)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
### Async Policy (MUST)
|
|
2
|
+
|
|
3
|
+
- Asynchronous code MUST use `async/await`.
|
|
4
|
+
- `.then()`/`.catch()` chains are not allowed for new code.
|
|
5
|
+
- Every async call chain MUST have explicit error handling at the boundary.
|
|
6
|
+
|
|
7
|
+
Good example:
|
|
8
|
+
|
|
9
|
+
- `ai/examples/rules/async-good.ts`
|
|
10
|
+
|
|
11
|
+
Bad example:
|
|
12
|
+
|
|
13
|
+
- `ai/examples/rules/async-bad.ts`
|