@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.
Files changed (34) hide show
  1. package/README.md +47 -9
  2. package/bin/code-standards.mjs +30 -36
  3. package/package.json +1 -1
  4. package/resources/ai/templates/examples/demo/src/billing/billing-service.ts +102 -0
  5. package/resources/ai/templates/examples/demo/src/invoices/invoice-errors.ts +89 -0
  6. package/resources/ai/templates/examples/demo/src/invoices/invoice-service.ts +123 -0
  7. package/resources/ai/templates/examples/demo/src/invoices/invoice-types.ts +20 -0
  8. package/resources/ai/templates/examples/rules/async-bad.ts +94 -0
  9. package/resources/ai/templates/examples/rules/async-good.ts +94 -0
  10. package/resources/ai/templates/examples/rules/class-first-bad.ts +90 -0
  11. package/resources/ai/templates/examples/rules/class-first-good.ts +99 -0
  12. package/resources/ai/templates/examples/rules/constructor-bad.ts +98 -0
  13. package/resources/ai/templates/examples/rules/constructor-good.ts +97 -0
  14. package/resources/ai/templates/examples/rules/control-flow-bad.ts +85 -0
  15. package/resources/ai/templates/examples/rules/control-flow-good.ts +92 -0
  16. package/resources/ai/templates/examples/rules/errors-bad.ts +86 -0
  17. package/resources/ai/templates/examples/rules/errors-good.ts +89 -0
  18. package/resources/ai/templates/examples/rules/functions-bad.ts +106 -0
  19. package/resources/ai/templates/examples/rules/functions-good.ts +102 -0
  20. package/resources/ai/templates/examples/rules/returns-bad.ts +92 -0
  21. package/resources/ai/templates/examples/rules/returns-good.ts +94 -0
  22. package/resources/ai/templates/examples/rules/testing-bad.ts +88 -0
  23. package/resources/ai/templates/examples/rules/testing-good.ts +92 -0
  24. package/resources/ai/templates/rules/architecture.md +5 -15
  25. package/resources/ai/templates/rules/async.md +2 -12
  26. package/resources/ai/templates/rules/class-first.md +23 -82
  27. package/resources/ai/templates/rules/control-flow.md +2 -8
  28. package/resources/ai/templates/rules/errors.md +2 -11
  29. package/resources/ai/templates/rules/functions.md +2 -15
  30. package/resources/ai/templates/rules/returns.md +2 -22
  31. package/resources/ai/templates/rules/testing.md +2 -14
  32. package/standards/style.md +9 -1
  33. package/templates/node-lib/gitignore +5 -0
  34. package/templates/node-service/gitignore +5 -0
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  If you just want to start now:
19
19
 
20
20
  ```bash
21
- npx @sha3/code-standards init my-api --template node-service --yes
21
+ npx @sha3/code-standards init --template node-service --yes
22
22
  ```
23
23
 
24
24
  Then in your AI chat, paste this:
@@ -64,7 +64,9 @@ Default generated contract includes structural class/file blocks such as:
64
64
 
65
65
  Section marker format is fixed to:
66
66
 
67
- - `/** @section <block-name> */`
67
+ - `/**`
68
+ - ` * @section <block-name>`
69
+ - ` */`
68
70
 
69
71
  All blocks MUST exist even when empty (`// empty`).
70
72
 
@@ -85,14 +87,14 @@ No.
85
87
  Default flow (no profile setup):
86
88
 
87
89
  ```bash
88
- npx @sha3/code-standards init my-app --template node-service --yes
90
+ npx @sha3/code-standards init --template node-service --yes
89
91
  ```
90
92
 
91
93
  Custom profile flow:
92
94
 
93
95
  ```bash
94
96
  npx @sha3/code-standards profile --profile ./profiles/team.profile.json
95
- npx @sha3/code-standards init my-app --template node-service --yes --profile ./profiles/team.profile.json
97
+ npx @sha3/code-standards init --template node-service --yes --profile ./profiles/team.profile.json
96
98
  ```
97
99
 
98
100
  ---
@@ -103,10 +105,43 @@ After `init`, your new repo contains:
103
105
 
104
106
  - `AGENTS.md` (blocking rules for AI)
105
107
  - `ai/codex.md`, `ai/cursor.md`, `ai/copilot.md`, `ai/windsurf.md`
108
+ - `ai/examples/rules/*.ts` (good/bad examples per rule)
109
+ - `ai/examples/demo/src/*` (feature-folder demo with classes and section blocks)
110
+ - `.gitignore` preconfigured for Node/TypeScript output
106
111
  - lint/format/typecheck/test-ready project template
107
112
 
108
113
  That means the next step is **not** configuring tools. The next step is telling your assistant to obey `AGENTS.md` before coding.
109
114
 
115
+ ## TypeScript Example Files
116
+
117
+ `init` now stores code examples in `.ts` files instead of embedding them inside `AGENTS.md`.
118
+
119
+ Demo structure generated by default:
120
+
121
+ - `ai/examples/demo/src/invoices/invoice-service.ts`
122
+ - `ai/examples/demo/src/invoices/invoice-errors.ts`
123
+ - `ai/examples/demo/src/invoices/invoice-types.ts`
124
+ - `ai/examples/demo/src/billing/billing-service.ts`
125
+
126
+ Rule-specific examples:
127
+
128
+ - `ai/examples/rules/class-first-good.ts`
129
+ - `ai/examples/rules/class-first-bad.ts`
130
+ - `ai/examples/rules/constructor-good.ts`
131
+ - `ai/examples/rules/constructor-bad.ts`
132
+ - `ai/examples/rules/functions-good.ts`
133
+ - `ai/examples/rules/functions-bad.ts`
134
+ - `ai/examples/rules/returns-good.ts`
135
+ - `ai/examples/rules/returns-bad.ts`
136
+ - `ai/examples/rules/async-good.ts`
137
+ - `ai/examples/rules/async-bad.ts`
138
+ - `ai/examples/rules/control-flow-good.ts`
139
+ - `ai/examples/rules/control-flow-bad.ts`
140
+ - `ai/examples/rules/errors-good.ts`
141
+ - `ai/examples/rules/errors-bad.ts`
142
+ - `ai/examples/rules/testing-good.ts`
143
+ - `ai/examples/rules/testing-bad.ts`
144
+
110
145
  ---
111
146
 
112
147
  ## How To Use With AI (Copy/Paste)
@@ -197,14 +232,16 @@ npx @sha3/code-standards profile \
197
232
 
198
233
  ### 2) Scaffold project
199
234
 
235
+ Run this inside the directory you want to initialize.
236
+
200
237
  ```bash
201
- npx @sha3/code-standards init my-api --template node-service --yes
238
+ npx @sha3/code-standards init --template node-service --yes
202
239
  ```
203
240
 
204
241
  With explicit profile:
205
242
 
206
243
  ```bash
207
- npx @sha3/code-standards init my-lib \
244
+ npx @sha3/code-standards init \
208
245
  --template node-lib \
209
246
  --yes \
210
247
  --no-install \
@@ -214,7 +251,7 @@ npx @sha3/code-standards init my-lib \
214
251
  Skip AI files when needed:
215
252
 
216
253
  ```bash
217
- npx @sha3/code-standards init my-lib --template node-lib --yes --no-ai-adapters
254
+ npx @sha3/code-standards init --template node-lib --yes --no-ai-adapters
218
255
  ```
219
256
 
220
257
  ### 3) Work loop inside generated project
@@ -234,15 +271,16 @@ Then use the prompts above in your AI tool.
234
271
  code-standards <command> [options]
235
272
 
236
273
  Commands:
237
- init [project-name] Initialize a project from templates
274
+ init Initialize a project in the current directory
238
275
  profile Create or update the AI style profile
239
276
  ```
240
277
 
241
278
  ### `init` options
242
279
 
280
+ `init` always uses the current working directory as target.
281
+
243
282
  - `--template <node-lib|node-service>`
244
283
  - `--yes`
245
- - `--target <dir>`
246
284
  - `--no-install`
247
285
  - `--force`
248
286
  - `--with-ai-adapters`
@@ -166,13 +166,12 @@ function printUsage() {
166
166
  code-standards <command> [options]
167
167
 
168
168
  Commands:
169
- init [project-name] Initialize a project from templates
169
+ init Initialize a project in the current directory
170
170
  profile Create or update the AI style profile
171
171
 
172
172
  Init options:
173
173
  --template <node-lib|node-service>
174
174
  --yes
175
- --target <dir>
176
175
  --no-install
177
176
  --force
178
177
  --with-ai-adapters
@@ -192,11 +191,9 @@ function parseInitArgs(argv) {
192
191
  const options = {
193
192
  template: undefined,
194
193
  yes: false,
195
- target: undefined,
196
194
  install: true,
197
195
  force: false,
198
196
  withAiAdapters: true,
199
- projectName: undefined,
200
197
  profilePath: undefined,
201
198
  help: false
202
199
  };
@@ -205,12 +202,9 @@ function parseInitArgs(argv) {
205
202
  const token = argv[i];
206
203
 
207
204
  if (!token.startsWith("-")) {
208
- if (!options.projectName) {
209
- options.projectName = token;
210
- continue;
211
- }
212
-
213
- throw new Error(`Unexpected positional argument: ${token}`);
205
+ throw new Error(
206
+ `Positional project names are not supported: ${token}. Run init from your target directory.`
207
+ );
214
208
  }
215
209
 
216
210
  if (token === "--template") {
@@ -230,15 +224,7 @@ function parseInitArgs(argv) {
230
224
  }
231
225
 
232
226
  if (token === "--target") {
233
- const value = argv[i + 1];
234
-
235
- if (!value || value.startsWith("-")) {
236
- throw new Error("Missing value for --target");
237
- }
238
-
239
- options.target = value;
240
- i += 1;
241
- continue;
227
+ throw new Error("--target is not supported. Run init from your target directory.");
242
228
  }
243
229
 
244
230
  if (token === "--profile") {
@@ -354,6 +340,15 @@ function replaceTokens(content, tokens) {
354
340
  return output;
355
341
  }
356
342
 
343
+ function mapTemplateFileName(fileName) {
344
+ // npm may rewrite .gitignore to .npmignore in published tarballs.
345
+ if (fileName === "gitignore" || fileName === ".npmignore") {
346
+ return ".gitignore";
347
+ }
348
+
349
+ return fileName;
350
+ }
351
+
357
352
  function normalizeProfile(rawProfile) {
358
353
  const normalized = {};
359
354
 
@@ -413,9 +408,9 @@ async function copyTemplateDirectory(sourceDir, targetDir, tokens) {
413
408
 
414
409
  for (const entry of entries) {
415
410
  const sourcePath = path.join(sourceDir, entry.name);
416
- const targetPath = path.join(targetDir, entry.name);
417
411
 
418
412
  if (entry.isDirectory()) {
413
+ const targetPath = path.join(targetDir, entry.name);
419
414
  await copyTemplateDirectory(sourcePath, targetPath, tokens);
420
415
  continue;
421
416
  }
@@ -426,6 +421,8 @@ async function copyTemplateDirectory(sourceDir, targetDir, tokens) {
426
421
 
427
422
  const raw = await readFile(sourcePath, "utf8");
428
423
  const rendered = replaceTokens(raw, tokens);
424
+ const targetName = mapTemplateFileName(entry.name);
425
+ const targetPath = path.join(targetDir, targetName);
429
426
  await writeFile(targetPath, rendered, "utf8");
430
427
  }
431
428
  }
@@ -790,9 +787,16 @@ async function renderAdapterFiles(packageRoot, targetDir, tokens) {
790
787
  }
791
788
  }
792
789
 
790
+ async function renderExampleFiles(packageRoot, targetDir, tokens) {
791
+ const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
792
+ const examplesTarget = path.join(targetDir, "ai", "examples");
793
+ await copyTemplateDirectory(examplesTemplateDir, examplesTarget, tokens);
794
+ }
795
+
793
796
  async function generateAiInstructions(packageRoot, targetDir, tokens, profile) {
794
797
  await renderProjectAgents(packageRoot, targetDir, tokens.projectName, profile);
795
798
  await renderAdapterFiles(packageRoot, targetDir, tokens);
799
+ await renderExampleFiles(packageRoot, targetDir, tokens);
796
800
  }
797
801
 
798
802
  async function maybeInitializeProfileInteractively(packageRoot, profilePath) {
@@ -881,17 +885,6 @@ async function promptForMissing(options) {
881
885
  resolved.template = normalized;
882
886
  }
883
887
 
884
- if (!resolved.projectName) {
885
- const nameAnswer = await rl.question("Project name [my-project]: ");
886
- resolved.projectName = nameAnswer.trim() || "my-project";
887
- }
888
-
889
- if (!resolved.target) {
890
- const targetDefault = resolved.projectName;
891
- const targetAnswer = await rl.question(`Target directory [${targetDefault}]: `);
892
- resolved.target = targetAnswer.trim() || targetDefault;
893
- }
894
-
895
888
  if (options.install) {
896
889
  const installAnswer = await rl.question("Install dependencies now? (Y/n): ");
897
890
  const normalized = installAnswer.trim().toLowerCase();
@@ -914,10 +907,12 @@ async function validateInitResources(packageRoot, templateName) {
914
907
  "agents.project.template.md"
915
908
  );
916
909
  const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
910
+ const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
917
911
 
918
912
  await access(templateDir, constants.R_OK);
919
913
  await access(agentsTemplatePath, constants.R_OK);
920
914
  await access(adaptersTemplateDir, constants.R_OK);
915
+ await access(examplesTemplateDir, constants.R_OK);
921
916
 
922
917
  return { templateDir };
923
918
  }
@@ -932,8 +927,6 @@ async function runInit(rawOptions) {
932
927
 
933
928
  if (options.yes) {
934
929
  options.template ??= "node-lib";
935
- options.projectName ??= "my-project";
936
- options.target ??= options.projectName;
937
930
  } else {
938
931
  options = await promptForMissing(options);
939
932
  }
@@ -948,9 +941,11 @@ async function runInit(rawOptions) {
948
941
  const schema = await loadProfileSchema(packageRoot);
949
942
  const profile = await resolveProfileForInit(packageRoot, options, schema);
950
943
 
951
- const projectName = options.projectName ?? path.basename(options.target ?? "my-project");
944
+ const targetPath = path.resolve(process.cwd());
945
+ const inferredProjectName = path.basename(targetPath);
946
+ const projectName =
947
+ inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
952
948
  const packageName = sanitizePackageName(projectName);
953
- const targetPath = path.resolve(process.cwd(), options.target ?? projectName);
954
949
 
955
950
  await ensureTargetReady(targetPath, options.force);
956
951
 
@@ -975,7 +970,6 @@ async function runInit(rawOptions) {
975
970
 
976
971
  console.log(`Project created at ${targetPath}`);
977
972
  console.log("Next steps:");
978
- console.log(` cd ${targetPath}`);
979
973
  console.log(" npm run check");
980
974
  }
981
975
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sha3/code-standards",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "AI-first code standards, tooling exports, and project initializer",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @section imports:externals
3
+ */
4
+
5
+ // empty
6
+
7
+ /**
8
+ * @section imports:internals
9
+ */
10
+
11
+ import type { InvoiceService } from "../invoices/invoice-service.js";
12
+ import type { InvoiceSummary } from "../invoices/invoice-types.js";
13
+
14
+ /**
15
+ * @section consts
16
+ */
17
+
18
+ const CURRENCY_SYMBOL = "$";
19
+
20
+ /**
21
+ * @section types
22
+ */
23
+
24
+ export type BillingSnapshot = {
25
+ customerId: string;
26
+ invoiceCount: number;
27
+ totalAmount: number;
28
+ formattedTotal: string;
29
+ };
30
+
31
+ export class BillingService {
32
+ /**
33
+ * @section private:attributes
34
+ */
35
+
36
+ // empty
37
+
38
+ /**
39
+ * @section private:properties
40
+ */
41
+
42
+ private readonly invoiceService: InvoiceService;
43
+
44
+ /**
45
+ * @section public:properties
46
+ */
47
+
48
+ // empty
49
+
50
+ /**
51
+ * @section constructor
52
+ */
53
+
54
+ public constructor(invoiceService: InvoiceService) {
55
+ this.invoiceService = invoiceService;
56
+ }
57
+
58
+ /**
59
+ * @section static:properties
60
+ */
61
+
62
+ // empty
63
+
64
+ /**
65
+ * @section factory
66
+ */
67
+
68
+ public static create(invoiceService: InvoiceService): BillingService {
69
+ const service = new BillingService(invoiceService);
70
+ return service;
71
+ }
72
+
73
+ /**
74
+ * @section private:methods
75
+ */
76
+
77
+ private formatCurrency(amount: number): string {
78
+ const formattedAmount = `${CURRENCY_SYMBOL}${amount.toFixed(2)}`;
79
+ return formattedAmount;
80
+ }
81
+
82
+ /**
83
+ * @section public:methods
84
+ */
85
+
86
+ public async snapshot(customerId: string): Promise<BillingSnapshot> {
87
+ const summary: InvoiceSummary = await this.invoiceService.summarizeForCustomer(customerId);
88
+ const snapshot: BillingSnapshot = {
89
+ customerId,
90
+ invoiceCount: summary.count,
91
+ totalAmount: summary.totalAmount,
92
+ formattedTotal: this.formatCurrency(summary.totalAmount)
93
+ };
94
+ return snapshot;
95
+ }
96
+
97
+ /**
98
+ * @section static:methods
99
+ */
100
+
101
+ // empty
102
+ }
@@ -0,0 +1,89 @@
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
+ // empty
24
+
25
+ export class InvalidInvoiceCommandError extends Error {
26
+ /**
27
+ * @section private:attributes
28
+ */
29
+
30
+ // empty
31
+
32
+ /**
33
+ * @section private:properties
34
+ */
35
+
36
+ private readonly reason: string;
37
+
38
+ /**
39
+ * @section public:properties
40
+ */
41
+
42
+ // empty
43
+
44
+ /**
45
+ * @section constructor
46
+ */
47
+
48
+ public constructor(reason: string) {
49
+ super(`Invalid invoice command: ${reason}`);
50
+ this.name = "InvalidInvoiceCommandError";
51
+ this.reason = reason;
52
+ }
53
+
54
+ /**
55
+ * @section static:properties
56
+ */
57
+
58
+ // empty
59
+
60
+ /**
61
+ * @section factory
62
+ */
63
+
64
+ public static forReason(reason: string): InvalidInvoiceCommandError {
65
+ const error = new InvalidInvoiceCommandError(reason);
66
+ return error;
67
+ }
68
+
69
+ /**
70
+ * @section private:methods
71
+ */
72
+
73
+ // empty
74
+
75
+ /**
76
+ * @section public:methods
77
+ */
78
+
79
+ public getReason(): string {
80
+ const value = this.reason;
81
+ return value;
82
+ }
83
+
84
+ /**
85
+ * @section static:methods
86
+ */
87
+
88
+ // empty
89
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @section imports:externals
3
+ */
4
+
5
+ import { randomUUID } from "node:crypto";
6
+
7
+ /**
8
+ * @section imports:internals
9
+ */
10
+
11
+ import { InvalidInvoiceCommandError } from "./invoice-errors.js";
12
+ import type { CreateInvoiceCommand, Invoice, InvoiceSummary } from "./invoice-types.js";
13
+
14
+ /**
15
+ * @section consts
16
+ */
17
+
18
+ const MINIMUM_INVOICE_AMOUNT = 0;
19
+
20
+ /**
21
+ * @section types
22
+ */
23
+
24
+ // empty
25
+
26
+ export class InvoiceService {
27
+ /**
28
+ * @section private:attributes
29
+ */
30
+
31
+ // empty
32
+
33
+ /**
34
+ * @section private:properties
35
+ */
36
+
37
+ private readonly invoicesById: Map<string, Invoice>;
38
+
39
+ /**
40
+ * @section public:properties
41
+ */
42
+
43
+ // empty
44
+
45
+ /**
46
+ * @section constructor
47
+ */
48
+
49
+ public constructor() {
50
+ this.invoicesById = new Map<string, Invoice>();
51
+ }
52
+
53
+ /**
54
+ * @section static:properties
55
+ */
56
+
57
+ // empty
58
+
59
+ /**
60
+ * @section factory
61
+ */
62
+
63
+ public static create(): InvoiceService {
64
+ const service = new InvoiceService();
65
+ return service;
66
+ }
67
+
68
+ /**
69
+ * @section private:methods
70
+ */
71
+
72
+ private validate(command: CreateInvoiceCommand): void {
73
+ if (!command.customerId.trim()) {
74
+ throw InvalidInvoiceCommandError.forReason("customerId is required");
75
+ }
76
+
77
+ if (command.amount <= MINIMUM_INVOICE_AMOUNT) {
78
+ throw InvalidInvoiceCommandError.forReason("amount must be greater than zero");
79
+ }
80
+ }
81
+
82
+ private toInvoice(command: CreateInvoiceCommand): Invoice {
83
+ const invoice: Invoice = {
84
+ id: randomUUID(),
85
+ customerId: command.customerId,
86
+ amount: command.amount,
87
+ issuedAt: new Date()
88
+ };
89
+ return invoice;
90
+ }
91
+
92
+ /**
93
+ * @section public:methods
94
+ */
95
+
96
+ public async create(command: CreateInvoiceCommand): Promise<Invoice> {
97
+ this.validate(command);
98
+ const createdInvoice: Invoice = this.toInvoice(command);
99
+ this.invoicesById.set(createdInvoice.id, createdInvoice);
100
+ return createdInvoice;
101
+ }
102
+
103
+ public async summarizeForCustomer(customerId: string): Promise<InvoiceSummary> {
104
+ const allInvoices: Invoice[] = Array.from(this.invoicesById.values());
105
+ const invoices: Invoice[] = allInvoices.filter((invoice) => {
106
+ return invoice.customerId === customerId;
107
+ });
108
+ const totalAmount = invoices.reduce((sum, invoice) => {
109
+ return sum + invoice.amount;
110
+ }, 0);
111
+ const summary: InvoiceSummary = {
112
+ count: invoices.length,
113
+ totalAmount
114
+ };
115
+ return summary;
116
+ }
117
+
118
+ /**
119
+ * @section static:methods
120
+ */
121
+
122
+ // empty
123
+ }
@@ -0,0 +1,20 @@
1
+ export type InvoiceId = string;
2
+
3
+ export type CustomerId = string;
4
+
5
+ export type Invoice = {
6
+ id: InvoiceId;
7
+ customerId: CustomerId;
8
+ amount: number;
9
+ issuedAt: Date;
10
+ };
11
+
12
+ export type CreateInvoiceCommand = {
13
+ customerId: CustomerId;
14
+ amount: number;
15
+ };
16
+
17
+ export type InvoiceSummary = {
18
+ count: number;
19
+ totalAmount: number;
20
+ };