@comity-dev/semgrep-rules 0.1.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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/build-config.d.ts +7 -0
- package/dist/build-config.js +19 -0
- package/dist/filesystem.d.ts +11 -0
- package/dist/filesystem.js +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/parse-yaml.d.ts +24 -0
- package/dist/parse-yaml.js +36 -0
- package/package.json +35 -0
- package/rules/adapter-contract-shape.yaml +38 -0
- package/rules/composition-shape.yaml +42 -0
- package/rules/core-domain-no-crypto.yaml +77 -0
- package/rules/error-class-shape.yaml +47 -0
- package/rules/module-shape.yaml +43 -0
- package/rules/value-object-shape.yaml +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Filippo Bovo and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @comity-dev/semgrep-rules
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Reusable Semgrep rule set for Comity structural source-code patterns. Semgrep is well-suited to **structural patterns** that ESLint cannot express cleanly: class shape, interface structure, method signatures, prohibited implementation patterns.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
The canonical rule files live under `./rules/` as YAML. The package also exports a loader that produces a single combined Semgrep config suitable for a single `--config` invocation.
|
|
10
|
+
|
|
11
|
+
## Ownership
|
|
12
|
+
|
|
13
|
+
Owned by `comity-development`. Consumed by `@comity-dev/validate` which actually invokes the `semgrep` binary.
|
|
14
|
+
|
|
15
|
+
The rule set targets **runtime** Core Modules and Adapters only. It does NOT apply to Development tooling packages (`comity.layer: "dev-tooling"`), which legitimately use Node APIs (`process.env`, etc.) that the rules forbid for runtime code.
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
listRuleFiles,
|
|
22
|
+
loadRule,
|
|
23
|
+
loadAllRules,
|
|
24
|
+
buildSemgrepConfig,
|
|
25
|
+
} from "@comity-dev/semgrep-rules";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `./rules` subpath exposes the raw YAML rule files:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import rulePath from "@comity-dev/semgrep-rules/rules/<file>.yaml";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Relationship to Comity Standards
|
|
35
|
+
|
|
36
|
+
- `adapters.md` §11 — Adapter contract implementation shape.
|
|
37
|
+
- `errors.md` — error class shape.
|
|
38
|
+
- `modules.md` — module surface shape.
|
|
39
|
+
|
|
40
|
+
## Development
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm build
|
|
44
|
+
pnpm test
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Tool availability
|
|
48
|
+
|
|
49
|
+
The `semgrep` binary is **optional**. When missing, the validator reports `TOOL-UNAVAILABLE` rather than fabricating PASS. Install via `pip install semgrep` or set `SEMGREP_BIN` to an absolute path.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a single combined Semgrep config file from all canonical rule
|
|
3
|
+
* files. Semgrep's CLI accepts a single `--config`; the validate engine
|
|
4
|
+
* writes the combined content to a temp file and invokes semgrep with
|
|
5
|
+
* that path.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildSemgrepConfig(): Promise<string>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { listRuleFiles } from "./filesystem.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a single combined Semgrep config file from all canonical rule
|
|
5
|
+
* files. Semgrep's CLI accepts a single `--config`; the validate engine
|
|
6
|
+
* writes the combined content to a temp file and invokes semgrep with
|
|
7
|
+
* that path.
|
|
8
|
+
*/
|
|
9
|
+
export async function buildSemgrepConfig() {
|
|
10
|
+
const files = await listRuleFiles();
|
|
11
|
+
const ruleBlocks = [];
|
|
12
|
+
for (const f of files) {
|
|
13
|
+
const text = await readFile(f, "utf8");
|
|
14
|
+
// Strip the top-level `rules:` line; we want just the array entries.
|
|
15
|
+
const body = text.replace(/^\s*rules:\s*\n/m, "");
|
|
16
|
+
ruleBlocks.push(body.trimEnd());
|
|
17
|
+
}
|
|
18
|
+
return `rules:\n${ruleBlocks.join("\n")}\n`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ParsedSemgrepRule } from "./parse-yaml.js";
|
|
2
|
+
export interface SemgrepRule extends ParsedSemgrepRule {
|
|
3
|
+
/** The absolute path of the rule file from which this rule was loaded. */
|
|
4
|
+
file: string;
|
|
5
|
+
}
|
|
6
|
+
/** Returns the canonical rule file paths under `./rules/`. */
|
|
7
|
+
export declare function listRuleFiles(): Promise<string[]>;
|
|
8
|
+
/** Loads and parses a single Semgrep rule file. */
|
|
9
|
+
export declare function loadRule(filePath: string): Promise<SemgrepRule[]>;
|
|
10
|
+
/** Loads every rule in the package and returns them as a flat array. */
|
|
11
|
+
export declare function loadAllRules(): Promise<SemgrepRule[]>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parseSemgrepYaml } from "./parse-yaml.js";
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const RULES_DIR = join(__dirname, "..", "rules");
|
|
7
|
+
/** Returns the canonical rule file paths under `./rules/`. */
|
|
8
|
+
export async function listRuleFiles() {
|
|
9
|
+
const entries = await readdir(RULES_DIR);
|
|
10
|
+
return entries
|
|
11
|
+
.filter((f) => f.endsWith(".yaml"))
|
|
12
|
+
.map((f) => join(RULES_DIR, f));
|
|
13
|
+
}
|
|
14
|
+
/** Loads and parses a single Semgrep rule file. */
|
|
15
|
+
export async function loadRule(filePath) {
|
|
16
|
+
const text = await readFile(filePath, "utf8");
|
|
17
|
+
return parseSemgrepYaml(text).map((r) => ({ ...r, file: filePath }));
|
|
18
|
+
}
|
|
19
|
+
/** Loads every rule in the package and returns them as a flat array. */
|
|
20
|
+
export async function loadAllRules() {
|
|
21
|
+
const files = await listRuleFiles();
|
|
22
|
+
const all = [];
|
|
23
|
+
for (const f of files) {
|
|
24
|
+
const rules = await loadRule(f);
|
|
25
|
+
all.push(...rules);
|
|
26
|
+
}
|
|
27
|
+
return all;
|
|
28
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helper for parsing Semgrep YAML rule files.
|
|
3
|
+
*
|
|
4
|
+
* The YAML format we target is a strict subset:
|
|
5
|
+
* rules:
|
|
6
|
+
* - id: <name>
|
|
7
|
+
* severity: <level>
|
|
8
|
+
* languages: [<lang>]
|
|
9
|
+
* message: |
|
|
10
|
+
* <text>
|
|
11
|
+
* pattern: |
|
|
12
|
+
* <pattern>
|
|
13
|
+
*
|
|
14
|
+
* We do not use a YAML library because the canonical rule files are
|
|
15
|
+
* authored by the Development repository and follow a known shape.
|
|
16
|
+
*/
|
|
17
|
+
export interface ParsedSemgrepRule {
|
|
18
|
+
id: string;
|
|
19
|
+
severity: string | null;
|
|
20
|
+
languages: string[] | null;
|
|
21
|
+
message: string | null;
|
|
22
|
+
raw: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function parseSemgrepYaml(text: string): ParsedSemgrepRule[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helper for parsing Semgrep YAML rule files.
|
|
3
|
+
*
|
|
4
|
+
* The YAML format we target is a strict subset:
|
|
5
|
+
* rules:
|
|
6
|
+
* - id: <name>
|
|
7
|
+
* severity: <level>
|
|
8
|
+
* languages: [<lang>]
|
|
9
|
+
* message: |
|
|
10
|
+
* <text>
|
|
11
|
+
* pattern: |
|
|
12
|
+
* <pattern>
|
|
13
|
+
*
|
|
14
|
+
* We do not use a YAML library because the canonical rule files are
|
|
15
|
+
* authored by the Development repository and follow a known shape.
|
|
16
|
+
*/
|
|
17
|
+
export function parseSemgrepYaml(text) {
|
|
18
|
+
const ruleBlockRe = /^[ \t]{2}- id:\s*(.+?)\n((?:^(?![ \t]{2}- id:|rules:|---).*\n?)*)/gm;
|
|
19
|
+
const rules = [];
|
|
20
|
+
let match;
|
|
21
|
+
while ((match = ruleBlockRe.exec(text)) !== null) {
|
|
22
|
+
const id = (match[1] ?? "").trim();
|
|
23
|
+
const body = match[2] ?? "";
|
|
24
|
+
const severityMatch = body.match(/^\s*severity:\s*(.+)$/m);
|
|
25
|
+
const languagesMatch = body.match(/languages:\s*\[(.+?)\]/m);
|
|
26
|
+
const messageMatch = body.match(/message:\s*\|?\s*\n((?:[ \t]+.+\n?)*)/m);
|
|
27
|
+
rules.push({
|
|
28
|
+
id,
|
|
29
|
+
severity: severityMatch?.[1]?.trim() ?? null,
|
|
30
|
+
languages: languagesMatch?.[1]?.split(",").map((s) => s.trim()) ?? null,
|
|
31
|
+
message: messageMatch?.[1]?.trim() ?? null,
|
|
32
|
+
raw: ` - id: ${id}\n${body}`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return rules;
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@comity-dev/semgrep-rules",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable Semgrep rules for Comity structural source-code patterns. Development tooling.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"comity": {
|
|
9
|
+
"layer": "dev-tooling"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24.0.0"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./rules": "./rules/"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"./dist",
|
|
25
|
+
"./rules"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^24.13.3",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: comity-adapter-must-declare-implements
|
|
3
|
+
languages: [typescript]
|
|
4
|
+
severity: ERROR
|
|
5
|
+
message: |
|
|
6
|
+
A Technology Adapter MUST `implements` exactly one Core Module contract.
|
|
7
|
+
The implementing class is the structural binding between the technology
|
|
8
|
+
and the contract.
|
|
9
|
+
- adapters.md §11
|
|
10
|
+
- ADR-007
|
|
11
|
+
pattern: |
|
|
12
|
+
class $NAME implements $I {
|
|
13
|
+
...
|
|
14
|
+
}
|
|
15
|
+
pattern-not: |
|
|
16
|
+
class $NAME implements $I {
|
|
17
|
+
constructor(...args) { ... }
|
|
18
|
+
...
|
|
19
|
+
}
|
|
20
|
+
metadata:
|
|
21
|
+
category: comity-architecture
|
|
22
|
+
standard: adapters.md
|
|
23
|
+
section: 11
|
|
24
|
+
|
|
25
|
+
- id: comity-adapter-no-raw-error-throw
|
|
26
|
+
languages: [typescript]
|
|
27
|
+
severity: WARNING
|
|
28
|
+
message: |
|
|
29
|
+
Adapters MAY throw platform errors, but the boundary they expose to Core
|
|
30
|
+
Modules MUST use Result<T, E>. Throwing raw Error from a Core-facing
|
|
31
|
+
method breaks the Result contract.
|
|
32
|
+
- errors.md §6
|
|
33
|
+
pattern: |
|
|
34
|
+
throw new Error($MSG);
|
|
35
|
+
metadata:
|
|
36
|
+
category: comity-architecture
|
|
37
|
+
standard: errors.md
|
|
38
|
+
section: 6
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: comity-no-static-state-in-core
|
|
3
|
+
languages: [typescript]
|
|
4
|
+
severity: WARNING
|
|
5
|
+
message: |
|
|
6
|
+
Core Modules MUST NOT use static class state. Composition owns wiring;
|
|
7
|
+
static state in Core breaks testability.
|
|
8
|
+
- architecture-principles.md
|
|
9
|
+
pattern: |
|
|
10
|
+
class $NAME {
|
|
11
|
+
...
|
|
12
|
+
static $F: $T = $V;
|
|
13
|
+
...
|
|
14
|
+
}
|
|
15
|
+
paths:
|
|
16
|
+
include:
|
|
17
|
+
- "**/packages/**/src/**/*.ts"
|
|
18
|
+
exclude:
|
|
19
|
+
- "**/packages/*/adapters/**"
|
|
20
|
+
- "**/node_modules/**"
|
|
21
|
+
metadata:
|
|
22
|
+
category: comity-architecture
|
|
23
|
+
standard: architecture-principles.md
|
|
24
|
+
|
|
25
|
+
- id: comity-no-singleton-instance
|
|
26
|
+
languages: [typescript]
|
|
27
|
+
severity: WARNING
|
|
28
|
+
message: |
|
|
29
|
+
Comity avoids singletons. Provide composition facades via the kernel's
|
|
30
|
+
`composition` module instead.
|
|
31
|
+
- architecture-principles.md
|
|
32
|
+
pattern: |
|
|
33
|
+
export const $NAME = new $CLASS(...$ARGS);
|
|
34
|
+
paths:
|
|
35
|
+
include:
|
|
36
|
+
- "**/packages/**/src/index.ts"
|
|
37
|
+
- "**/packages/**/src/contracts/index.ts"
|
|
38
|
+
exclude:
|
|
39
|
+
- "**/node_modules/**"
|
|
40
|
+
metadata:
|
|
41
|
+
category: comity-architecture
|
|
42
|
+
standard: architecture-principles.md
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: comity-core-no-process-env
|
|
3
|
+
languages: [typescript]
|
|
4
|
+
severity: WARNING
|
|
5
|
+
message: |
|
|
6
|
+
Core Modules MUST NOT read `process.env` directly — environment access
|
|
7
|
+
belongs in adapters. Inject configuration via composition facades.
|
|
8
|
+
- architecture-validation.md §4
|
|
9
|
+
- configuration.md
|
|
10
|
+
pattern: process.env[$KEY]
|
|
11
|
+
paths:
|
|
12
|
+
include:
|
|
13
|
+
- "**/packages/**/src/**/*.ts"
|
|
14
|
+
exclude:
|
|
15
|
+
- "**/packages/*/adapters/**"
|
|
16
|
+
- "**/node_modules/**"
|
|
17
|
+
metadata:
|
|
18
|
+
category: comity-architecture
|
|
19
|
+
standard: configuration.md
|
|
20
|
+
|
|
21
|
+
- id: comity-core-no-fs
|
|
22
|
+
languages: [typescript]
|
|
23
|
+
severity: WARNING
|
|
24
|
+
message: |
|
|
25
|
+
Core Modules MUST NOT import Node's `fs` API. File system access belongs
|
|
26
|
+
in adapters.
|
|
27
|
+
- architecture-validation.md §4
|
|
28
|
+
- adapters.md §11
|
|
29
|
+
pattern: import { ... } from "node:fs"
|
|
30
|
+
paths:
|
|
31
|
+
include:
|
|
32
|
+
- "**/packages/**/src/**/*.ts"
|
|
33
|
+
exclude:
|
|
34
|
+
- "**/packages/*/adapters/**"
|
|
35
|
+
- "**/node_modules/**"
|
|
36
|
+
metadata:
|
|
37
|
+
category: comity-architecture
|
|
38
|
+
standard: adapters.md
|
|
39
|
+
section: 11
|
|
40
|
+
|
|
41
|
+
- id: comity-core-no-fs-promises
|
|
42
|
+
languages: [typescript]
|
|
43
|
+
severity: WARNING
|
|
44
|
+
message: |
|
|
45
|
+
Core Modules MUST NOT import `node:fs/promises`. File system access belongs
|
|
46
|
+
in adapters.
|
|
47
|
+
- architecture-validation.md §4
|
|
48
|
+
- adapters.md §11
|
|
49
|
+
pattern: import { ... } from "node:fs/promises"
|
|
50
|
+
paths:
|
|
51
|
+
include:
|
|
52
|
+
- "**/packages/**/src/**/*.ts"
|
|
53
|
+
exclude:
|
|
54
|
+
- "**/packages/*/adapters/**"
|
|
55
|
+
- "**/node_modules/**"
|
|
56
|
+
metadata:
|
|
57
|
+
category: comity-architecture
|
|
58
|
+
standard: adapters.md
|
|
59
|
+
section: 11
|
|
60
|
+
|
|
61
|
+
- id: comity-no-console-log
|
|
62
|
+
languages: [typescript]
|
|
63
|
+
severity: WARNING
|
|
64
|
+
message: |
|
|
65
|
+
Core Modules MUST NOT use `console.log` for telemetry. Use the framework's
|
|
66
|
+
logger abstraction.
|
|
67
|
+
- coding.md
|
|
68
|
+
pattern: console.log(...$ARGS)
|
|
69
|
+
paths:
|
|
70
|
+
include:
|
|
71
|
+
- "**/packages/**/src/**/*.ts"
|
|
72
|
+
exclude:
|
|
73
|
+
- "**/packages/*/adapters/**"
|
|
74
|
+
- "**/node_modules/**"
|
|
75
|
+
metadata:
|
|
76
|
+
category: comity-architecture
|
|
77
|
+
standard: coding.md
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: comity-error-class-must-extend-base-error
|
|
3
|
+
languages: [typescript]
|
|
4
|
+
severity: ERROR
|
|
5
|
+
message: |
|
|
6
|
+
Every Comity error class MUST extend `@comity/primitives/errors/BaseError`
|
|
7
|
+
or a documented domain base. Standalone Error classes lose the structured
|
|
8
|
+
`code` field and break cross-package error handling.
|
|
9
|
+
- errors.md §6
|
|
10
|
+
- public-api.md §3.1
|
|
11
|
+
patterns:
|
|
12
|
+
- pattern: |
|
|
13
|
+
class $NAME extends Error { ... }
|
|
14
|
+
- pattern-not: |
|
|
15
|
+
class $NAME extends BaseError { ... }
|
|
16
|
+
- pattern-not: |
|
|
17
|
+
class $NAME extends $BASE { ... }
|
|
18
|
+
- pattern-not-inside: |
|
|
19
|
+
/** ... */
|
|
20
|
+
...
|
|
21
|
+
class $NAME { ... }
|
|
22
|
+
metadata:
|
|
23
|
+
category: comity-architecture
|
|
24
|
+
standard: errors.md
|
|
25
|
+
section: 6
|
|
26
|
+
|
|
27
|
+
- id: comity-error-class-must-have-code-field
|
|
28
|
+
languages: [typescript]
|
|
29
|
+
severity: WARNING
|
|
30
|
+
message: |
|
|
31
|
+
A Comity error class SHOULD expose a stable `code` field for cross-package
|
|
32
|
+
error matching.
|
|
33
|
+
- errors.md §6
|
|
34
|
+
pattern: |
|
|
35
|
+
class $NAME extends BaseError {
|
|
36
|
+
...
|
|
37
|
+
}
|
|
38
|
+
pattern-not: |
|
|
39
|
+
class $NAME extends BaseError {
|
|
40
|
+
...
|
|
41
|
+
readonly code: $T;
|
|
42
|
+
...
|
|
43
|
+
}
|
|
44
|
+
metadata:
|
|
45
|
+
category: comity-architecture
|
|
46
|
+
standard: errors.md
|
|
47
|
+
section: 6
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: comity-module-no-default-export
|
|
3
|
+
languages: [typescript]
|
|
4
|
+
severity: WARNING
|
|
5
|
+
message: |
|
|
6
|
+
Comity Core Modules MUST use named exports, not default exports. Default
|
|
7
|
+
exports obscure the public surface and break tree-shaking guarantees.
|
|
8
|
+
- public-api.md §3
|
|
9
|
+
- architecture-validation.md §4
|
|
10
|
+
pattern: export default $X
|
|
11
|
+
paths:
|
|
12
|
+
include:
|
|
13
|
+
- "**/packages/**/src/index.ts"
|
|
14
|
+
- "**/packages/**/src/contracts/index.ts"
|
|
15
|
+
exclude:
|
|
16
|
+
- "**/node_modules/**"
|
|
17
|
+
metadata:
|
|
18
|
+
category: comity-architecture
|
|
19
|
+
standard: public-api.md
|
|
20
|
+
section: 3
|
|
21
|
+
|
|
22
|
+
- id: comity-module-must-have-internal-marker
|
|
23
|
+
languages: [typescript]
|
|
24
|
+
severity: INFO
|
|
25
|
+
message: |
|
|
26
|
+
Files intended to be internal-only SHOULD be located under an `internal/`
|
|
27
|
+
or `lazy/` directory; the ESLint rule `@comity/no-forbidden-deep-import`
|
|
28
|
+
enforces the reverse — consumers MUST NOT reach into these paths.
|
|
29
|
+
- public-api.md §3.2
|
|
30
|
+
patterns:
|
|
31
|
+
- pattern: |
|
|
32
|
+
/** @internal */
|
|
33
|
+
...
|
|
34
|
+
- pattern-not-inside: |
|
|
35
|
+
/** @internal */
|
|
36
|
+
...
|
|
37
|
+
...
|
|
38
|
+
/** @internal */
|
|
39
|
+
...
|
|
40
|
+
metadata:
|
|
41
|
+
category: comity-architecture
|
|
42
|
+
standard: public-api.md
|
|
43
|
+
section: "3.2"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: comity-value-object-must-be-readonly
|
|
3
|
+
languages: [typescript]
|
|
4
|
+
severity: WARNING
|
|
5
|
+
message: |
|
|
6
|
+
Value objects MUST be immutable. All fields SHOULD be `readonly`.
|
|
7
|
+
- domain-modeling.md
|
|
8
|
+
pattern: |
|
|
9
|
+
class $NAME {
|
|
10
|
+
...
|
|
11
|
+
constructor(...) {
|
|
12
|
+
...
|
|
13
|
+
this.$F = $V;
|
|
14
|
+
...
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
pattern-not: |
|
|
18
|
+
class $NAME {
|
|
19
|
+
...
|
|
20
|
+
constructor(...) {
|
|
21
|
+
...
|
|
22
|
+
this.$F: readonly $T = $V;
|
|
23
|
+
...
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
paths:
|
|
27
|
+
include:
|
|
28
|
+
- "**/packages/**/src/domain/**/*.ts"
|
|
29
|
+
exclude:
|
|
30
|
+
- "**/node_modules/**"
|
|
31
|
+
metadata:
|
|
32
|
+
category: comity-domain-modeling
|
|
33
|
+
standard: domain-modeling.md
|
|
34
|
+
|
|
35
|
+
- id: comity-entity-must-emit-events
|
|
36
|
+
languages: [typescript]
|
|
37
|
+
severity: INFO
|
|
38
|
+
message: |
|
|
39
|
+
Domain entities SHOULD expose a typed event surface via the `@comity/primitives/events`
|
|
40
|
+
primitive, not raw EventEmitter.
|
|
41
|
+
- events.md
|
|
42
|
+
pattern: |
|
|
43
|
+
class $NAME {
|
|
44
|
+
...
|
|
45
|
+
new EventEmitter();
|
|
46
|
+
...
|
|
47
|
+
}
|
|
48
|
+
paths:
|
|
49
|
+
include:
|
|
50
|
+
- "**/packages/**/src/domain/**/*.ts"
|
|
51
|
+
- "**/packages/**/src/core/**/*.ts"
|
|
52
|
+
exclude:
|
|
53
|
+
- "**/node_modules/**"
|
|
54
|
+
metadata:
|
|
55
|
+
category: comity-events
|
|
56
|
+
standard: events.md
|