pi-gitnexus-fork 0.7.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/.github/workflows/ci.yml +20 -0
- package/.gitnexusignore +11 -0
- package/.sg-rules/async-function-must-await-or-return.yml +55 -0
- package/.sg-rules/catch-must-log-error.yml +78 -0
- package/.sg-rules/class-must-implement-or-extend.yml +61 -0
- package/.sg-rules/class-property-must-be-readonly.yml +61 -0
- package/.sg-rules/error-must-extend-base.yml +56 -0
- package/.sg-rules/generic-must-be-constrained.yml +60 -0
- package/.sg-rules/import-reexport-risk.yml +9 -0
- package/.sg-rules/missing-session-id-in-api.yml +16 -0
- package/.sg-rules/no-any-in-generic-args.yml +57 -0
- package/.sg-rules/no-await-in-promise-all.yml +28 -0
- package/.sg-rules/no-barrel-export.yml +17 -0
- package/.sg-rules/no-bq-write-in-module.yml +65 -0
- package/.sg-rules/no-console-except-error.yml +27 -0
- package/.sg-rules/no-console-in-server.yml +42 -0
- package/.sg-rules/no-empty-catch.yml +20 -0
- package/.sg-rules/no-empty-function.yml +24 -0
- package/.sg-rules/no-eval.yml +28 -0
- package/.sg-rules/no-explicit-any.yml +34 -0
- package/.sg-rules/no-hardcoded-placeholder-string.yml +23 -0
- package/.sg-rules/no-hardcoded-secrets.yml +32 -0
- package/.sg-rules/no-innerHTML.yml +22 -0
- package/.sg-rules/no-json-parse-without-trycatch.yml +33 -0
- package/.sg-rules/no-magic-numbers.yml +25 -0
- package/.sg-rules/no-nested-ternary.yml +21 -0
- package/.sg-rules/no-non-null-assertion.yml +25 -0
- package/.sg-rules/no-stub-implementation.yml +44 -0
- package/.sg-rules/no-throw-literal.yml +50 -0
- package/.sg-rules/no-todo-comment.yml +24 -0
- package/.sg-rules/no-ts-ignore-comment.yml +48 -0
- package/.sg-rules/no-type-assertion-in-jsx.yml +23 -0
- package/.sg-rules/no-unguarded-trim.yml +24 -0
- package/.sg-rules/no-unknown-without-narrowing.yml +76 -0
- package/.sg-rules/no-unsafe-bracket-access.yml +58 -0
- package/.sg-rules/no-unsafe-type-assertion.yml +45 -0
- package/.sg-rules/switch-must-be-exhaustive.yml +62 -0
- package/.sg-rules/zod-async-refine-without-abort.yml +62 -0
- package/.sg-rules/zod-enum-unsafe-access.yml +59 -0
- package/.sg-rules/zod-nested-object-deep-path.yml +70 -0
- package/.sg-rules/zod-optional-without-default-in-route.yml +50 -0
- package/.sg-rules/zod-parse-not-safe.yml +42 -0
- package/.sg-rules/zod-preprocess-without-fallback.yml +58 -0
- package/.sg-rules/zod-refine-no-return-undefined.yml +54 -0
- package/.sg-rules/zod-transform-without-output-type.yml +52 -0
- package/.sg-sha +1 -0
- package/.sgignore +4 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +99 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/biome.json +25 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +890 -0
- package/coverage/coverage-final.json +12 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/augment-remote.ts.html +274 -0
- package/coverage/src/gitnexus.ts.html +1363 -0
- package/coverage/src/index.html +236 -0
- package/coverage/src/index.ts.html +1561 -0
- package/coverage/src/mcp-client-factory.ts.html +367 -0
- package/coverage/src/mcp-client-stdio.ts.html +736 -0
- package/coverage/src/mcp-client.ts.html +568 -0
- package/coverage/src/remote-mcp-client.ts.html +709 -0
- package/coverage/src/repo-resolver.ts.html +526 -0
- package/coverage/src/tools.ts.html +970 -0
- package/coverage/src/ui/index.html +131 -0
- package/coverage/src/ui/main-menu.ts.html +502 -0
- package/coverage/src/ui/settings-menu.ts.html +460 -0
- package/dist/augment-remote.d.ts +11 -0
- package/dist/augment-remote.js +55 -0
- package/dist/gitnexus.d.ts +103 -0
- package/dist/gitnexus.js +410 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +479 -0
- package/dist/mcp-client-factory.d.ts +19 -0
- package/dist/mcp-client-factory.js +78 -0
- package/dist/mcp-client-stdio.d.ts +35 -0
- package/dist/mcp-client-stdio.js +186 -0
- package/dist/mcp-client.d.ts +45 -0
- package/dist/mcp-client.js +145 -0
- package/dist/remote-mcp-client.d.ts +43 -0
- package/dist/remote-mcp-client.js +181 -0
- package/dist/repo-resolver.d.ts +47 -0
- package/dist/repo-resolver.js +123 -0
- package/dist/tools.d.ts +6 -0
- package/dist/tools.js +230 -0
- package/dist/ui/main-menu.d.ts +33 -0
- package/dist/ui/main-menu.js +102 -0
- package/dist/ui/settings-menu.d.ts +16 -0
- package/dist/ui/settings-menu.js +95 -0
- package/docs/design/remote-mcp-backend.md +153 -0
- package/media/screenshot.png +0 -0
- package/package.json +61 -0
- package/sgconfig.yml +4 -0
- package/skills/gitnexus-debugging/SKILL.md +84 -0
- package/skills/gitnexus-exploring/SKILL.md +73 -0
- package/skills/gitnexus-impact-analysis/SKILL.md +93 -0
- package/skills/gitnexus-pr-review/SKILL.md +109 -0
- package/skills/gitnexus-refactoring/SKILL.md +85 -0
- package/src/augment-remote.ts +63 -0
- package/src/gitnexus.ts +426 -0
- package/src/index.ts +492 -0
- package/src/mcp-client-factory.ts +94 -0
- package/src/mcp-client-stdio.ts +217 -0
- package/src/mcp-client.ts +208 -0
- package/src/remote-mcp-client.ts +250 -0
- package/src/repo-resolver.ts +147 -0
- package/src/tools.ts +295 -0
- package/src/ui/main-menu.ts +139 -0
- package/src/ui/settings-menu.ts +125 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: 20
|
|
17
|
+
cache: npm
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm run lint
|
|
20
|
+
- run: npm run typecheck
|
package/.gitnexusignore
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# SOURCE: https://www.typescriptlang.org/docs/handbook/2/functions.html#return-type-void
|
|
2
|
+
# "void means the function's return value will not be observed."
|
|
3
|
+
# SOURCE: https://typescript-book.com/function-return-type-annotation
|
|
4
|
+
# "async function without Promise<void> return type may accidentally return
|
|
5
|
+
# a value that becomes a floating promise."
|
|
6
|
+
# SOURCE: https://www.totaltypescript.com/tips/avoid-async-function-without-await
|
|
7
|
+
# "async functions that don't await are likely bugs — or should not be async."
|
|
8
|
+
#
|
|
9
|
+
# REFACTORED: Replaced broken `not` pattern approach (multi-line $$$PRE/$$$POST)
|
|
10
|
+
# with `has: { kind: await_expression, stopBy: end }` which correctly searches
|
|
11
|
+
# the entire function body subtree. Reduced FP rate from ~80% to near-zero.
|
|
12
|
+
id: async-function-must-await-or-return
|
|
13
|
+
language: typescript
|
|
14
|
+
message: "async function with no await — remove async keyword or add await. Pointless async creates unnecessary Promise wrapping"
|
|
15
|
+
severity: warning
|
|
16
|
+
note: |
|
|
17
|
+
async functions that never await are misleading. They create unnecessary Promise wrapping
|
|
18
|
+
and microtask overhead. Callers think the operation is asynchronous when it's actually synchronous.
|
|
19
|
+
|
|
20
|
+
WHY: async functions create a new microtask and wrap the return in a Promise. If the
|
|
21
|
+
function body has no await and no IO, the async keyword is misleading: callers think
|
|
22
|
+
the operation is asynchronous (and may race) when it's actually synchronous.
|
|
23
|
+
rule:
|
|
24
|
+
any:
|
|
25
|
+
# Regular async function declarations
|
|
26
|
+
- kind: function_declaration
|
|
27
|
+
regex: "^async\\s"
|
|
28
|
+
not:
|
|
29
|
+
any:
|
|
30
|
+
- has:
|
|
31
|
+
kind: await_expression
|
|
32
|
+
stopBy: end
|
|
33
|
+
- has:
|
|
34
|
+
kind: for_in_statement
|
|
35
|
+
stopBy: end
|
|
36
|
+
# Async arrow functions — anchor regex to start of function text
|
|
37
|
+
# to avoid matching outer non-async functions containing inner async callbacks
|
|
38
|
+
- kind: arrow_function
|
|
39
|
+
regex: "^async\\s"
|
|
40
|
+
not:
|
|
41
|
+
any:
|
|
42
|
+
- has:
|
|
43
|
+
kind: await_expression
|
|
44
|
+
stopBy: end
|
|
45
|
+
- has:
|
|
46
|
+
kind: for_in_statement
|
|
47
|
+
stopBy: end
|
|
48
|
+
ignores:
|
|
49
|
+
- '**/*.test.ts'
|
|
50
|
+
- '**/*.spec.ts'
|
|
51
|
+
- '**/test/**'
|
|
52
|
+
- '**/tests/**'
|
|
53
|
+
- '**/__tests__/**'
|
|
54
|
+
- '**/scripts/**'
|
|
55
|
+
- 'test-*.ts'
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# SOURCE: https://rules.sonarsource.com/typescript/RSPEC-2486/
|
|
2
|
+
# SonarQube S2486: "Empty catch blocks should not be left empty"
|
|
3
|
+
# SOURCE: https://eslint.org/docs/latest/rules/no-empty
|
|
4
|
+
# ESLint no-empty: Empty catch blocks swallow errors silently.
|
|
5
|
+
# SOURCE: https://github.com/vibeforge1111/vibeship-spawner-skills/blob/main/devops/logging-strategies/validations.yaml
|
|
6
|
+
# Community pattern: catch blocks must log the error, not swallow it.
|
|
7
|
+
#
|
|
8
|
+
# REFACTORED: Reduced FP rate from ~80% to <15% by excluding:
|
|
9
|
+
# - catch blocks with ANY comment (comments = intentional)
|
|
10
|
+
# - catch blocks that surface errors via instanceof checks
|
|
11
|
+
# - catch blocks that surface errors via template strings (error info to caller)
|
|
12
|
+
# - catch blocks that re-throw
|
|
13
|
+
# - catch blocks that use ui.notify, console.*, logger.*, process.stderr
|
|
14
|
+
id: catch-must-log-error
|
|
15
|
+
language: typescript
|
|
16
|
+
message: "Catch block must log the error — silently swallowing makes debugging impossible"
|
|
17
|
+
severity: warning
|
|
18
|
+
note: |
|
|
19
|
+
Catch blocks that silently swallow errors make debugging impossible.
|
|
20
|
+
If the error is intentionally ignored, add a comment explaining why.
|
|
21
|
+
If the error is surfaced to the caller via structured error response, add logging too.
|
|
22
|
+
rule:
|
|
23
|
+
kind: catch_clause
|
|
24
|
+
not:
|
|
25
|
+
any:
|
|
26
|
+
# Has console.error/warn/log/info
|
|
27
|
+
- has:
|
|
28
|
+
pattern: console.error($$$)
|
|
29
|
+
stopBy: end
|
|
30
|
+
- has:
|
|
31
|
+
pattern: console.warn($$$)
|
|
32
|
+
stopBy: end
|
|
33
|
+
- has:
|
|
34
|
+
pattern: console.log($$$)
|
|
35
|
+
stopBy: end
|
|
36
|
+
- has:
|
|
37
|
+
pattern: console.info($$$)
|
|
38
|
+
stopBy: end
|
|
39
|
+
# Has any-identifier.error/warn (logger patterns)
|
|
40
|
+
- has:
|
|
41
|
+
pattern: $LOGGER.error($$$)
|
|
42
|
+
stopBy: end
|
|
43
|
+
- has:
|
|
44
|
+
pattern: $LOGGER.warn($$$)
|
|
45
|
+
stopBy: end
|
|
46
|
+
# Has process.stderr.write
|
|
47
|
+
- has:
|
|
48
|
+
pattern: process.stderr.write($$$)
|
|
49
|
+
stopBy: end
|
|
50
|
+
# Has ui.notify or similar notification
|
|
51
|
+
- has:
|
|
52
|
+
pattern: $OBJ.notify($$$)
|
|
53
|
+
stopBy: end
|
|
54
|
+
# Re-throws the error
|
|
55
|
+
- has:
|
|
56
|
+
pattern: throw $ERR
|
|
57
|
+
stopBy: end
|
|
58
|
+
# Has any comment (comments indicate intentional handling)
|
|
59
|
+
- has:
|
|
60
|
+
kind: comment
|
|
61
|
+
stopBy: end
|
|
62
|
+
# Uses instanceof check on error (structured error handling)
|
|
63
|
+
- has:
|
|
64
|
+
pattern: $X instanceof Error
|
|
65
|
+
stopBy: end
|
|
66
|
+
# Has template string (typically surfaces error info to caller)
|
|
67
|
+
- has:
|
|
68
|
+
kind: template_string
|
|
69
|
+
stopBy: end
|
|
70
|
+
ignores:
|
|
71
|
+
- '**/*.test.ts'
|
|
72
|
+
- '**/*.spec.ts'
|
|
73
|
+
- '**/*.test-d.ts'
|
|
74
|
+
- '**/test/**'
|
|
75
|
+
- '**/tests/**'
|
|
76
|
+
- '**/__tests__/**'
|
|
77
|
+
- '**/scripts/**'
|
|
78
|
+
- 'test-*.ts'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SOURCE: https://www.typescriptlang.org/docs/handbook/2/classes.html#implements-clauses
|
|
2
|
+
# "Classes should implement interfaces to enforce contractual shape. Standalone
|
|
3
|
+
# classes without interface contracts create implicit APIs that drift over time."
|
|
4
|
+
# SOURCE: https://typescript-book.com/classes/implements
|
|
5
|
+
# "implements establishes a HAS-A contract. Every exported class should implement
|
|
6
|
+
# an interface — this enables dependency injection, mocking, and polymorphism."
|
|
7
|
+
# SOURCE: https://en.wikipedia.org/wiki/SOLID#Interface_segregation_principle
|
|
8
|
+
# SOLID Interface Segregation: "Define contracts via interfaces, implement via classes."
|
|
9
|
+
id: class-must-implement-or-extend
|
|
10
|
+
language: typescript
|
|
11
|
+
message: "Standalone class without extends or implements — must extend a base class or implement an interface for contract enforcement"
|
|
12
|
+
severity: warning
|
|
13
|
+
note: |
|
|
14
|
+
UNSAFE:
|
|
15
|
+
export class UserService {
|
|
16
|
+
async getUser(id: string) { ... }
|
|
17
|
+
async createUser(data: UserData) { ... }
|
|
18
|
+
}
|
|
19
|
+
// No contract — shape is implicit, can't mock, can't swap implementation
|
|
20
|
+
|
|
21
|
+
SAFE:
|
|
22
|
+
interface IUserService {
|
|
23
|
+
getUser(id: string): Promise<User>;
|
|
24
|
+
createUser(data: UserData): Promise<User>;
|
|
25
|
+
}
|
|
26
|
+
export class UserService implements IUserService {
|
|
27
|
+
async getUser(id: string): Promise<User> { ... }
|
|
28
|
+
async createUser(data: UserData): Promise<User> { ... }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// OR extend a base class:
|
|
32
|
+
export class UserService extends BaseService { ... }
|
|
33
|
+
|
|
34
|
+
WHY: Standalone exported classes create implicit APIs. Without an interface or base class:
|
|
35
|
+
(1) can't generate mocks for testing, (2) can't swap implementations for DI,
|
|
36
|
+
(3) method signatures can silently drift, (4) consumers couple to implementation not contract.
|
|
37
|
+
Every exported class should either implement an interface or extend a base class.
|
|
38
|
+
rule:
|
|
39
|
+
pattern: export class $NAME { $$$BODY }
|
|
40
|
+
not:
|
|
41
|
+
any:
|
|
42
|
+
# Safe: implements an interface
|
|
43
|
+
- pattern: export class $NAME implements $$$INTERFACES { $$$BODY }
|
|
44
|
+
# Safe: extends a base class
|
|
45
|
+
- pattern: export class $NAME extends $BASE { $$$BODY }
|
|
46
|
+
# Safe: abstract base classes
|
|
47
|
+
- pattern: export abstract class $NAME { $$$BODY }
|
|
48
|
+
# Safe: type-only/utility classes with only static members
|
|
49
|
+
- pattern: |
|
|
50
|
+
export class $NAME {
|
|
51
|
+
static $$$STATIC_MEMBERS
|
|
52
|
+
}
|
|
53
|
+
ignores:
|
|
54
|
+
- '**/*.test.ts'
|
|
55
|
+
- '**/*.spec.ts'
|
|
56
|
+
- '**/test/**'
|
|
57
|
+
- '**/tests/**'
|
|
58
|
+
- '**/__tests__/**'
|
|
59
|
+
- '**/scripts/**'
|
|
60
|
+
- 'test-*.ts'
|
|
61
|
+
- '**/node_modules/**'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SOURCE: https://www.typescriptlang.org/docs/handbook/2/classes.html#readonly
|
|
2
|
+
# "Use readonly to prevent reassignment of properties that should be immutable."
|
|
3
|
+
# SOURCE: https://typescript-book.com/readonly
|
|
4
|
+
# "Properties that are only assigned in the constructor should be readonly.
|
|
5
|
+
# Mutable class fields are a common source of bugs in concurrent/async code."
|
|
6
|
+
# SOURCE: https://en.wikipedia.org/wiki/Immutable_object
|
|
7
|
+
# "Immutable objects are inherently thread-safe and eliminate an entire class of bugs."
|
|
8
|
+
id: class-property-must-be-readonly
|
|
9
|
+
language: typescript
|
|
10
|
+
message: "Class property only assigned in constructor — mark as readonly to prevent accidental mutation"
|
|
11
|
+
severity: warning
|
|
12
|
+
note: |
|
|
13
|
+
UNSAFE:
|
|
14
|
+
class UserService {
|
|
15
|
+
private db: PrismaClient;
|
|
16
|
+
private config: Config;
|
|
17
|
+
constructor(db: PrismaClient, config: Config) {
|
|
18
|
+
this.db = db;
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// db and config can be reassigned anywhere — mutable by accident
|
|
23
|
+
|
|
24
|
+
SAFE:
|
|
25
|
+
class UserService {
|
|
26
|
+
private readonly db: PrismaClient;
|
|
27
|
+
private readonly config: Config;
|
|
28
|
+
constructor(db: PrismaClient, config: Config) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
this.config = config;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
WHY: Class properties that are only set in the constructor are effectively immutable.
|
|
35
|
+
Without readonly, any method can reassign them — causing subtle bugs:
|
|
36
|
+
(1) async methods may read stale values after concurrent mutation,
|
|
37
|
+
(2) dependency injection containers expect immutable references,
|
|
38
|
+
(3) readonly signals intent clearly — this is a configuration, not state.
|
|
39
|
+
rule:
|
|
40
|
+
pattern: |
|
|
41
|
+
class $CLASSNAME {
|
|
42
|
+
$$$PRE
|
|
43
|
+
private $PROP: $TYPE = $VALUE
|
|
44
|
+
$$$POST
|
|
45
|
+
}
|
|
46
|
+
not:
|
|
47
|
+
any:
|
|
48
|
+
- pattern: |
|
|
49
|
+
class $CLASSNAME {
|
|
50
|
+
$$$PRE
|
|
51
|
+
private readonly $PROP: $TYPE = $VALUE
|
|
52
|
+
$$$POST
|
|
53
|
+
}
|
|
54
|
+
ignores:
|
|
55
|
+
- '**/*.test.ts'
|
|
56
|
+
- '**/*.spec.ts'
|
|
57
|
+
- '**/test/**'
|
|
58
|
+
- '**/tests/**'
|
|
59
|
+
- '**/__tests__/**'
|
|
60
|
+
- '**/scripts/**'
|
|
61
|
+
- 'test-*.ts'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# SOURCE: https://typescript-book.com/exceptions/your-own-error
|
|
2
|
+
# "Create a base error class for your domain. All custom errors should extend it,
|
|
3
|
+
# not the native Error. This enables centralized error handling, error codes, and
|
|
4
|
+
# structured error serialization."
|
|
5
|
+
# SOURCE: https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript
|
|
6
|
+
# "Subclass Error for different error categories (AppError, NetworkError, etc.)
|
|
7
|
+
# so catch blocks can discriminate by type."
|
|
8
|
+
# SOURCE: https://www.typescriptlang.org/docs/handbook/2/classes.html#extends-clauses
|
|
9
|
+
# TypeScript class inheritance: "extends establishes an IS-A relationship."
|
|
10
|
+
id: error-must-extend-base
|
|
11
|
+
language: typescript
|
|
12
|
+
message: "Custom error class extends raw Error — extend your domain base error class instead (e.g., AppError, DomainError) for centralized error handling and error codes"
|
|
13
|
+
severity: error
|
|
14
|
+
note: |
|
|
15
|
+
UNSAFE:
|
|
16
|
+
class NotFoundError extends Error { }
|
|
17
|
+
class ValidationError extends Error { }
|
|
18
|
+
class AuthError extends Error { }
|
|
19
|
+
// Each has its own shape, no common error code, no centralized serialization
|
|
20
|
+
|
|
21
|
+
SAFE:
|
|
22
|
+
class AppError extends Error {
|
|
23
|
+
constructor(public code: string, message: string, public statusCode: number) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = this.constructor.name;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class NotFoundError extends AppError {
|
|
29
|
+
constructor(resource: string) { super('NOT_FOUND', `${resource} not found`, 404); }
|
|
30
|
+
}
|
|
31
|
+
class ValidationError extends AppError {
|
|
32
|
+
constructor(field: string) { super('VALIDATION', `Invalid ${field}`, 400); }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
WHY: Direct `extends Error` produces error classes with no common interface. You can't:
|
|
36
|
+
(1) serialize errors uniformly for API responses, (2) catch by category (AppError vs
|
|
37
|
+
third-party Error), (3) attach error codes for i18n, (4) map to HTTP status codes
|
|
38
|
+
centrally. A domain base class enforces all errors carry code + message + status.
|
|
39
|
+
rule:
|
|
40
|
+
pattern: class $NAME extends Error { $$$BODY }
|
|
41
|
+
not:
|
|
42
|
+
any:
|
|
43
|
+
# Safe: this IS the base class itself (class AppError extends Error)
|
|
44
|
+
- pattern: class AppError extends Error { $$$BODY }
|
|
45
|
+
- pattern: class BaseError extends Error { $$$BODY }
|
|
46
|
+
- pattern: class DomainError extends Error { $$$BODY }
|
|
47
|
+
- pattern: class CustomError extends Error { $$$BODY }
|
|
48
|
+
ignores:
|
|
49
|
+
- '**/*.test.ts'
|
|
50
|
+
- '**/*.spec.ts'
|
|
51
|
+
- '**/test/**'
|
|
52
|
+
- '**/tests/**'
|
|
53
|
+
- '**/__tests__/**'
|
|
54
|
+
- '**/scripts/**'
|
|
55
|
+
- 'test-*.ts'
|
|
56
|
+
- '**/node_modules/**'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SOURCE: https://www.typescriptlang.org/docs/handbook/2/generics.html
|
|
2
|
+
# "Generic type parameters should be constrained. Unconstrained generics
|
|
3
|
+
# are as permissive as any — they accept everything."
|
|
4
|
+
# SOURCE: https://typescript-book.com/generics/constraints
|
|
5
|
+
# "Use extends to constrain generic parameters. <T> alone accepts any type,
|
|
6
|
+
# which defeats type safety. <T extends Entity> enforces a contract."
|
|
7
|
+
# SOURCE: https://www.totaltypescript.com/tips/constrain-generic-type-parameters
|
|
8
|
+
# "Unconstrained generics are a smell. If T can be anything, you don't need generics."
|
|
9
|
+
id: generic-must-be-constrained
|
|
10
|
+
language: typescript
|
|
11
|
+
message: "Unconstrained generic <T> — add extends constraint (e.g., <T extends Entity>) or use unknown directly"
|
|
12
|
+
severity: warning
|
|
13
|
+
note: |
|
|
14
|
+
UNSAFE:
|
|
15
|
+
function parse<T>(data: string): T { return JSON.parse(data); }
|
|
16
|
+
// T can be ANYTHING — no type safety, caller can assert anything
|
|
17
|
+
|
|
18
|
+
class Store<T> {
|
|
19
|
+
private items: T[] = [];
|
|
20
|
+
}
|
|
21
|
+
// T is unconstrained — Store<null> is valid, Store<undefined> is valid
|
|
22
|
+
|
|
23
|
+
SAFE:
|
|
24
|
+
function parse<T extends Record<string, unknown>>(data: string): T {
|
|
25
|
+
return JSON.parse(data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Entity { id: string }
|
|
29
|
+
class Store<T extends Entity> {
|
|
30
|
+
private items: T[] = [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If you truly want "any type", use unknown explicitly:
|
|
34
|
+
function identity(value: unknown): unknown { return value; }
|
|
35
|
+
|
|
36
|
+
WHY: Unconstrained `<T>` accepts any type, making it as unsafe as `any` in practice.
|
|
37
|
+
The caller can pass `T = any` and bypass all checks. Constraining with `extends`
|
|
38
|
+
enforces that T has a minimum shape. If no constraint is needed, `unknown` is more
|
|
39
|
+
honest than `<T>`.
|
|
40
|
+
rule:
|
|
41
|
+
any:
|
|
42
|
+
- pattern: function $NAME<$T>($$$PARAMS) { $$$BODY }
|
|
43
|
+
- pattern: export function $NAME<$T>($$$PARAMS) { $$$BODY }
|
|
44
|
+
- pattern: async function $NAME<$T>($$$PARAMS) { $$$BODY }
|
|
45
|
+
- pattern: export async function $NAME<$T>($$$PARAMS) { $$$BODY }
|
|
46
|
+
not:
|
|
47
|
+
any:
|
|
48
|
+
# Safe: constrained generic
|
|
49
|
+
- pattern: function $NAME<$T extends $CONSTRAINT>($$$PARAMS) { $$$BODY }
|
|
50
|
+
- pattern: export function $NAME<$T extends $CONSTRAINT>($$$PARAMS) { $$$BODY }
|
|
51
|
+
- pattern: async function $NAME<$T extends $CONSTRAINT>($$$PARAMS) { $$$BODY }
|
|
52
|
+
- pattern: export async function $NAME<$T extends $CONSTRAINT>($$$PARAMS) { $$$BODY }
|
|
53
|
+
ignores:
|
|
54
|
+
- '**/*.test.ts'
|
|
55
|
+
- '**/*.spec.ts'
|
|
56
|
+
- '**/test/**'
|
|
57
|
+
- '**/tests/**'
|
|
58
|
+
- '**/__tests__/**'
|
|
59
|
+
- '**/scripts/**'
|
|
60
|
+
- 'test-*.ts'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id: import-reexport-risk
|
|
2
|
+
language: typescript
|
|
3
|
+
rule:
|
|
4
|
+
all:
|
|
5
|
+
- pattern: import { $ITEM } from '$SOURCE'
|
|
6
|
+
- has:
|
|
7
|
+
pattern: export { $$$ $ITEM $$$ }
|
|
8
|
+
message: "Importing a symbol then immediately re-exporting it from the same file. This pattern often masks circular dependency chains during refactoring. Use 'export { $ITEM } from \"$SOURCE\"' directly or move shared logic to a lower layer."
|
|
9
|
+
severity: warning
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id: missing-session-id-in-api
|
|
2
|
+
language: typescript
|
|
3
|
+
rule:
|
|
4
|
+
all:
|
|
5
|
+
- any:
|
|
6
|
+
- pattern: Response.json($OBJ)
|
|
7
|
+
- pattern: json($_, $OBJ)
|
|
8
|
+
- not:
|
|
9
|
+
has:
|
|
10
|
+
pattern: "sessionId: $_"
|
|
11
|
+
- inside:
|
|
12
|
+
any:
|
|
13
|
+
- pattern: if ($_ === "/api/plan") { $$$ }
|
|
14
|
+
- pattern: if ($_ === "/api/diff") { $$$ }
|
|
15
|
+
message: "API response for /api/plan or /api/diff is missing sessionId. This breaks the 'Current' session badge."
|
|
16
|
+
severity: error
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SOURCE: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
|
|
2
|
+
# "Use discriminated unions instead of optional properties. They make state
|
|
3
|
+
# transitions explicit and prevent impossible states."
|
|
4
|
+
# SOURCE: https://kentcdodds.com/blog/make-impossible-states-impossible
|
|
5
|
+
# "Design types that make invalid states unrepresentable. Use discriminated
|
|
6
|
+
# unions, not loosely overlapping optional fields."
|
|
7
|
+
# SOURCE: https://github.com/gcanti/io-ts/blob/master/README.md
|
|
8
|
+
# "Runtime type validation + discriminated unions = total type safety."
|
|
9
|
+
id: no-any-in-generic-args
|
|
10
|
+
language: typescript
|
|
11
|
+
message: "Generic type instantiated with 'any' — use concrete type, unknown, or generic parameter instead. 'any' in generics bypasses all type checking"
|
|
12
|
+
severity: error
|
|
13
|
+
note: |
|
|
14
|
+
UNSAFE:
|
|
15
|
+
const items: Array<any> = [];
|
|
16
|
+
const map: Map<string, any> = new Map();
|
|
17
|
+
const record: Record<string, any> = {};
|
|
18
|
+
function parse<T>(input: any): T { ... }
|
|
19
|
+
|
|
20
|
+
SAFE:
|
|
21
|
+
const items: Array<User> = [];
|
|
22
|
+
const map: Map<string, User> = new Map();
|
|
23
|
+
const record: Record<string, User> = {};
|
|
24
|
+
function parse<T>(input: unknown): T { ... }
|
|
25
|
+
|
|
26
|
+
// If you genuinely don't know the type:
|
|
27
|
+
const items: Array<unknown> = [];
|
|
28
|
+
const record: Record<string, unknown> = {};
|
|
29
|
+
|
|
30
|
+
WHY: `any` inside generic type arguments (Array<any>, Map<K, any>, Record<string, any>)
|
|
31
|
+
creates containers that accept anything and provide no type information on retrieval.
|
|
32
|
+
This is worse than top-level any because it's hidden inside the generic — you think
|
|
33
|
+
you have a typed container but it's actually untyped. Use `unknown` if the type is
|
|
34
|
+
genuinely unknown, or concrete types if known.
|
|
35
|
+
rule:
|
|
36
|
+
any:
|
|
37
|
+
- pattern: Array<any>
|
|
38
|
+
- pattern: Map<any, any>
|
|
39
|
+
- pattern: Map<$KEY, any>
|
|
40
|
+
- pattern: Map<any, $VAL>
|
|
41
|
+
- pattern: 'Record<string, any>'
|
|
42
|
+
- pattern: 'Record<$KEY, any>'
|
|
43
|
+
- pattern: Set<any>
|
|
44
|
+
- pattern: Promise<any>
|
|
45
|
+
- pattern: ReadonlyArray<any>
|
|
46
|
+
- pattern: Partial<any>
|
|
47
|
+
- pattern: Required<any>
|
|
48
|
+
- pattern: Readonly<any>
|
|
49
|
+
ignores:
|
|
50
|
+
- '**/*.test.ts'
|
|
51
|
+
- '**/*.spec.ts'
|
|
52
|
+
- '**/test/**'
|
|
53
|
+
- '**/tests/**'
|
|
54
|
+
- '**/__tests__/**'
|
|
55
|
+
- '**/scripts/**'
|
|
56
|
+
- 'test-*.ts'
|
|
57
|
+
- '**/*.d.ts'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SOURCE: https://ast-grep.github.io/catalog/typescript/#no-await-in-promise-all
|
|
2
|
+
# Authored by: ast-grep (ast-grep/ast-grep), improved with stopBy + member-call exclusion
|
|
3
|
+
# Intention: F08-dependency-gate-async
|
|
4
|
+
id: no-await-in-promise-all
|
|
5
|
+
language: TypeScript
|
|
6
|
+
message: "Avoid await inside Promise.all — pass promises directly. Dependency gates (member calls) are allowed."
|
|
7
|
+
severity: warning
|
|
8
|
+
rule:
|
|
9
|
+
pattern: await $FUNC($$$ARGS)
|
|
10
|
+
inside:
|
|
11
|
+
pattern: Promise.all($_)
|
|
12
|
+
stopBy:
|
|
13
|
+
not: { any: [{kind: array}, {kind: arguments}] }
|
|
14
|
+
constraints:
|
|
15
|
+
FUNC:
|
|
16
|
+
# Only flag simple identifier calls (await fetchA()).
|
|
17
|
+
# Member calls (await obj.method()) are ALLOWED — they represent dependency gates
|
|
18
|
+
# where the instance must be resolved before calling the method.
|
|
19
|
+
# See intention F08 for rationale.
|
|
20
|
+
kind: identifier
|
|
21
|
+
ignores:
|
|
22
|
+
- '**/*.test.ts'
|
|
23
|
+
- '**/*.spec.ts'
|
|
24
|
+
- '**/test/**'
|
|
25
|
+
- '**/tests/**'
|
|
26
|
+
- '**/__tests__/**'
|
|
27
|
+
- '**/scripts/**'
|
|
28
|
+
- 'test-*.ts'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SOURCE: https://github.com/formatjs/formatjs/blob/master/sg-rules/no-barrel-export.yml
|
|
2
|
+
# Authored by: formatjs/formatjs
|
|
3
|
+
id: no-barrel-export
|
|
4
|
+
language: typescript
|
|
5
|
+
message: "Barrel exports (export * from) are banned. Use deep imports instead to reduce bundle size."
|
|
6
|
+
severity: error
|
|
7
|
+
url: https://github.com/formatjs/formatjs/blob/master/sg-rules/no-barrel-export.yml
|
|
8
|
+
rule:
|
|
9
|
+
pattern: "export * from '$PATH'"
|
|
10
|
+
ignores:
|
|
11
|
+
- '**/*.test.ts'
|
|
12
|
+
- '**/*.spec.ts'
|
|
13
|
+
- '**/test/**'
|
|
14
|
+
- '**/tests/**'
|
|
15
|
+
- '**/__tests__/**'
|
|
16
|
+
- '**/scripts/**'
|
|
17
|
+
- 'test-*.ts'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# SOURCE: https://cloud.google.com/bigquery/docs/best-practices-performance-overview
|
|
2
|
+
# Google Cloud: "Batch writes via the BigQuery Storage Write API or load jobs
|
|
3
|
+
# should be isolated in dedicated write services, not scattered across module code."
|
|
4
|
+
# SOURCE: https://cloud.google.com/bigquery/docs/loading-data
|
|
5
|
+
# BigQuery docs: table.insert() and table.load() are write operations that should
|
|
6
|
+
# be routed through a write layer to centralize error handling, retries, and auditing.
|
|
7
|
+
#
|
|
8
|
+
# CONTEXT: ALL mod-* modules (mod-contractor-payment, mod-*, …) must NOT call
|
|
9
|
+
# BigQuery write APIs directly. All writes (insert, load, createTable,
|
|
10
|
+
# write-queries) must go through a dedicated write service so that retry logic,
|
|
11
|
+
# error classification, and audit logging are centralized. Direct calls from
|
|
12
|
+
# module code bypass these safeguards and make write operations invisible to
|
|
13
|
+
# the write service's monitoring and alerting pipeline.
|
|
14
|
+
#
|
|
15
|
+
# SCOPE: Applied at common/typescript/ level — covers ALL mod-* modules
|
|
16
|
+
# (and any future repo using BigQuery). Constraint regex filters out
|
|
17
|
+
# non-BQ false positives (only matches BQ-like variable names).
|
|
18
|
+
id: no-bq-write-in-module
|
|
19
|
+
language: typescript
|
|
20
|
+
message: "BQ write API calls in module code — write operations must go through a dedicated write service, not called directly from module code"
|
|
21
|
+
severity: error
|
|
22
|
+
note: |
|
|
23
|
+
UNSAFE — direct write calls from module code:
|
|
24
|
+
import { Table } from "@google-cloud/bigquery";
|
|
25
|
+
const table: Table = bigquery.dataset("my_ds").table("my_tbl");
|
|
26
|
+
table.insert([{id: 1}]); // streaming insert
|
|
27
|
+
table.load("gs://bucket/data.csv"); // load job
|
|
28
|
+
dataset.createTable("new_tbl", { schema: "..." }); // DDL
|
|
29
|
+
bq.query("INSERT INTO t VALUES (1)"); // write query
|
|
30
|
+
|
|
31
|
+
SAFE — route through write service:
|
|
32
|
+
import { writeService } from "../services/bq-write-service";
|
|
33
|
+
await writeService.insert("my_ds.my_tbl", rows);
|
|
34
|
+
await writeService.load("my_ds.my_tbl", uri, options);
|
|
35
|
+
await writeService.query("INSERT INTO t VALUES (1)");
|
|
36
|
+
|
|
37
|
+
WHY: Direct BigQuery write calls from module code bypass centralized error
|
|
38
|
+
handling, retry policies, rate limiting, and audit logging. The write service
|
|
39
|
+
provides structured error classification, exponential backoff, and
|
|
40
|
+
dead-letter queuing. Scattered writes are invisible to monitoring and
|
|
41
|
+
impossible to roll back consistently.
|
|
42
|
+
|
|
43
|
+
CONSTRAINTS: Variable names are matched via regex to reduce false positives.
|
|
44
|
+
Only variable names that look like BigQuery objects (e.g., table, bqTable,
|
|
45
|
+
bq_table, dataset, bq, bigquery, bqClient) are flagged. Generic method calls
|
|
46
|
+
like `list.insert()` or `data.load()` are NOT flagged.
|
|
47
|
+
rule:
|
|
48
|
+
any:
|
|
49
|
+
# table.insert() / table.load() — streaming insert and load jobs
|
|
50
|
+
- pattern: $OBJ.insert($$$ARGS)
|
|
51
|
+
- pattern: $OBJ.load($$$ARGS)
|
|
52
|
+
# dataset.createTable() — DDL
|
|
53
|
+
- pattern: $OBJ.createTable($$$ARGS)
|
|
54
|
+
# bq.query() / bigquery.query() — may contain write SQL
|
|
55
|
+
- pattern: $OBJ.query($$$ARGS)
|
|
56
|
+
constraints:
|
|
57
|
+
OBJ:
|
|
58
|
+
regex: '(?i)^((bq|bigquery)[_\\s]?(table|dataset|client)?|table|dataset)$'
|
|
59
|
+
ignores:
|
|
60
|
+
- '**/*.test.ts'
|
|
61
|
+
- '**/*.spec.ts'
|
|
62
|
+
- '**/test/**'
|
|
63
|
+
- '**/tests/**'
|
|
64
|
+
- '**/__tests__/**'
|
|
65
|
+
- '**/scripts/**'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SOURCE: https://eslint.org/docs/latest/rules/no-console
|
|
2
|
+
# ESLint bans ALL console by default but provides allow: ["warn","error"] option.
|
|
3
|
+
# No mature tool does context-aware detection (catch_clause, .catch(), etc).
|
|
4
|
+
# SOURCE: https://github.com/eslint/eslint/issues/2621
|
|
5
|
+
# Community consensus: console.warn and console.error are production-safe.
|
|
6
|
+
# SOURCE: https://ast-grep.github.io/catalog/typescript/no-console-except-catch.html
|
|
7
|
+
# Original catalog rule — replaced with method-level allowlist approach.
|
|
8
|
+
id: no-console-except-error
|
|
9
|
+
language: typescript
|
|
10
|
+
message: "Use a proper logger instead of console.log/debug/trace/info. console.warn and console.error are allowed."
|
|
11
|
+
severity: warning
|
|
12
|
+
note: "Mirrors ESLint no-console with allow: ['warn','error']. Only bans debug-only methods."
|
|
13
|
+
rule:
|
|
14
|
+
pattern: console.$METHOD($$$)
|
|
15
|
+
constraints:
|
|
16
|
+
METHOD:
|
|
17
|
+
regex: 'log|debug|trace|info'
|
|
18
|
+
ignores:
|
|
19
|
+
- '**/*.test.ts'
|
|
20
|
+
- '**/*.spec.ts'
|
|
21
|
+
- '**/*.test-d.ts'
|
|
22
|
+
- '**/test/**'
|
|
23
|
+
- '**/tests/**'
|
|
24
|
+
- '**/__tests__/**'
|
|
25
|
+
- '**/__mocks__/**'
|
|
26
|
+
- '**/scripts/**'
|
|
27
|
+
- 'test-*.ts'
|