@esoteric-logic/praxis-harness 2.6.0 → 2.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/base/configs/ci/lint-stack.yml +126 -0
- package/base/configs/linters/.markdownlint.json +7 -0
- package/base/configs/linters/.pre-commit-config.yaml +63 -0
- package/base/configs/linters/biome.json +41 -0
- package/base/configs/linters/clippy.toml +4 -0
- package/base/configs/linters/ruff.toml +26 -0
- package/base/configs/linters/rustfmt.toml +7 -0
- package/base/configs/linters/semgrep.yml +65 -0
- package/base/configs/registry.json +9 -1
- package/base/hooks/quality-check.sh +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Polyglot Lint Stack — 3-Layer CI Template
|
|
2
|
+
# Copy to: .github/workflows/lint-stack.yml
|
|
3
|
+
# Conditional: each tool runs only if its language files exist in the repo.
|
|
4
|
+
# Pin actions to SHA — update hashes when upgrading versions.
|
|
5
|
+
|
|
6
|
+
name: Lint Stack
|
|
7
|
+
on:
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [main, develop]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
# ─────────────────────────────────────────────
|
|
17
|
+
# LAYER 1: Fast Feedback (~10s per tool)
|
|
18
|
+
# ─────────────────────────────────────────────
|
|
19
|
+
lint:
|
|
20
|
+
name: L1 — Lint + Format
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
24
|
+
|
|
25
|
+
# Python — Ruff
|
|
26
|
+
- name: Ruff check
|
|
27
|
+
if: hashFiles('**/*.py') != ''
|
|
28
|
+
uses: astral-sh/ruff-action@9700c1666704e06e2cf8f9046755e3dfc6297e40 # v3.2.1
|
|
29
|
+
with:
|
|
30
|
+
args: "check"
|
|
31
|
+
- name: Ruff format check
|
|
32
|
+
if: hashFiles('**/*.py') != ''
|
|
33
|
+
uses: astral-sh/ruff-action@9700c1666704e06e2cf8f9046755e3dfc6297e40 # v3.2.1
|
|
34
|
+
with:
|
|
35
|
+
args: "format --check"
|
|
36
|
+
|
|
37
|
+
# JS/TS — Biome
|
|
38
|
+
- name: Setup Biome
|
|
39
|
+
if: hashFiles('**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx') != ''
|
|
40
|
+
uses: biomejs/setup-biome@1cbe33ead22c7a2fded3b52fa2893611c815c3d2 # v2.5.0
|
|
41
|
+
- name: Biome CI
|
|
42
|
+
if: hashFiles('**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx') != ''
|
|
43
|
+
run: biome ci .
|
|
44
|
+
|
|
45
|
+
# Go — golangci-lint
|
|
46
|
+
- name: golangci-lint
|
|
47
|
+
if: hashFiles('go.mod') != ''
|
|
48
|
+
uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0
|
|
49
|
+
with:
|
|
50
|
+
version: v2.1
|
|
51
|
+
|
|
52
|
+
# Rust — Clippy
|
|
53
|
+
- name: Setup Rust toolchain
|
|
54
|
+
if: hashFiles('Cargo.toml') != ''
|
|
55
|
+
uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 # stable
|
|
56
|
+
with:
|
|
57
|
+
toolchain: stable
|
|
58
|
+
components: clippy, rustfmt
|
|
59
|
+
- name: Clippy
|
|
60
|
+
if: hashFiles('Cargo.toml') != ''
|
|
61
|
+
run: cargo clippy --all-targets --all-features -- -D warnings
|
|
62
|
+
- name: rustfmt check
|
|
63
|
+
if: hashFiles('Cargo.toml') != ''
|
|
64
|
+
run: cargo fmt --all -- --check
|
|
65
|
+
|
|
66
|
+
# Shell — ShellCheck
|
|
67
|
+
- name: ShellCheck
|
|
68
|
+
if: hashFiles('**/*.sh') != ''
|
|
69
|
+
run: |
|
|
70
|
+
sudo apt-get install -y shellcheck
|
|
71
|
+
find . -name '*.sh' -not -path './node_modules/*' -not -path './vendor/*' | xargs shellcheck -s bash
|
|
72
|
+
|
|
73
|
+
# ─────────────────────────────────────────────
|
|
74
|
+
# LAYER 2: Quality Gates (~2min)
|
|
75
|
+
# ─────────────────────────────────────────────
|
|
76
|
+
security-scan:
|
|
77
|
+
name: L2 — Semgrep
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
needs: lint
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
82
|
+
- uses: semgrep/semgrep-action@713efdd345ae26c72e79b5b32927be8e0e6bab83 # v1
|
|
83
|
+
with:
|
|
84
|
+
config: >-
|
|
85
|
+
p/default
|
|
86
|
+
p/secrets
|
|
87
|
+
p/owasp-top-ten
|
|
88
|
+
env:
|
|
89
|
+
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
|
90
|
+
|
|
91
|
+
# Uncomment to add SonarQube quality gate (requires self-hosted server)
|
|
92
|
+
# sonarqube:
|
|
93
|
+
# name: L2 — SonarQube
|
|
94
|
+
# runs-on: ubuntu-latest
|
|
95
|
+
# needs: lint
|
|
96
|
+
# steps:
|
|
97
|
+
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
98
|
+
# with:
|
|
99
|
+
# fetch-depth: 0
|
|
100
|
+
# - uses: SonarSource/sonarqube-scan-action@0e1a25e90571a34e2ec5c72ee40ba45cc73a1e6e # v4
|
|
101
|
+
# env:
|
|
102
|
+
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
103
|
+
# SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
104
|
+
# - uses: SonarSource/sonarqube-quality-gate-action@dc2f7b0dd95544cd550de3066f25f47e3fc20894 # v1.1.0
|
|
105
|
+
# timeout-minutes: 5
|
|
106
|
+
# env:
|
|
107
|
+
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
108
|
+
|
|
109
|
+
# ─────────────────────────────────────────────
|
|
110
|
+
# LAYER 3: AI Review (~2min, optional)
|
|
111
|
+
# ─────────────────────────────────────────────
|
|
112
|
+
# Uncomment to enable AI-powered PR review.
|
|
113
|
+
# Requires OPENAI_API_KEY secret or self-hosted Ollama.
|
|
114
|
+
#
|
|
115
|
+
# ai-review:
|
|
116
|
+
# name: L3 — AI Review
|
|
117
|
+
# runs-on: ubuntu-latest
|
|
118
|
+
# needs: [security-scan]
|
|
119
|
+
# steps:
|
|
120
|
+
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
121
|
+
# - uses: Codium-ai/pr-agent@main
|
|
122
|
+
# env:
|
|
123
|
+
# OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
124
|
+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
125
|
+
# with:
|
|
126
|
+
# command: review
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
# General file hygiene
|
|
3
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
4
|
+
rev: v5.0.0
|
|
5
|
+
hooks:
|
|
6
|
+
- id: trailing-whitespace
|
|
7
|
+
- id: end-of-file-fixer
|
|
8
|
+
- id: check-yaml
|
|
9
|
+
- id: check-json
|
|
10
|
+
- id: check-added-large-files
|
|
11
|
+
args: ['--maxkb=500']
|
|
12
|
+
- id: detect-private-key
|
|
13
|
+
- id: check-merge-conflict
|
|
14
|
+
|
|
15
|
+
# Layer 1: Python — Ruff
|
|
16
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
17
|
+
rev: v0.11.6
|
|
18
|
+
hooks:
|
|
19
|
+
- id: ruff
|
|
20
|
+
args: [--fix]
|
|
21
|
+
- id: ruff-format
|
|
22
|
+
|
|
23
|
+
# Layer 1: JS/TS — Biome
|
|
24
|
+
- repo: https://github.com/biomejs/pre-commit
|
|
25
|
+
rev: v2.0.0
|
|
26
|
+
hooks:
|
|
27
|
+
- id: biome-check
|
|
28
|
+
additional_dependencies: ["@biomejs/biome@2.0.0"]
|
|
29
|
+
|
|
30
|
+
# Layer 1: Shell — ShellCheck
|
|
31
|
+
- repo: https://github.com/shellcheck-py/shellcheck-py
|
|
32
|
+
rev: v0.10.0.1
|
|
33
|
+
hooks:
|
|
34
|
+
- id: shellcheck
|
|
35
|
+
args: [-s, bash]
|
|
36
|
+
|
|
37
|
+
# Layer 1: Go — golangci-lint
|
|
38
|
+
- repo: https://github.com/golangci/golangci-lint
|
|
39
|
+
rev: v2.1.0
|
|
40
|
+
hooks:
|
|
41
|
+
- id: golangci-lint
|
|
42
|
+
|
|
43
|
+
# Layer 1: Rust — rustfmt + clippy
|
|
44
|
+
- repo: local
|
|
45
|
+
hooks:
|
|
46
|
+
- id: rustfmt
|
|
47
|
+
name: rustfmt
|
|
48
|
+
entry: rustfmt
|
|
49
|
+
language: system
|
|
50
|
+
types: [rust]
|
|
51
|
+
- id: clippy
|
|
52
|
+
name: clippy
|
|
53
|
+
entry: cargo clippy -- -D warnings
|
|
54
|
+
language: system
|
|
55
|
+
types: [rust]
|
|
56
|
+
pass_filenames: false
|
|
57
|
+
|
|
58
|
+
# Layer 2: Semgrep security scan
|
|
59
|
+
- repo: https://github.com/semgrep/semgrep
|
|
60
|
+
rev: v1.120.0
|
|
61
|
+
hooks:
|
|
62
|
+
- id: semgrep
|
|
63
|
+
args: ['--config', 'auto', '--error']
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
|
|
3
|
+
"organizeImports": { "enabled": true },
|
|
4
|
+
"linter": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"rules": {
|
|
7
|
+
"recommended": true,
|
|
8
|
+
"complexity": {
|
|
9
|
+
"noExcessiveCognitiveComplexity": {
|
|
10
|
+
"level": "warn",
|
|
11
|
+
"options": { "maxAllowedComplexity": 15 }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"suspicious": {
|
|
15
|
+
"noExplicitAny": "warn",
|
|
16
|
+
"noConsoleLog": "warn"
|
|
17
|
+
},
|
|
18
|
+
"correctness": {
|
|
19
|
+
"noUnusedVariables": "error",
|
|
20
|
+
"noUnusedImports": "error"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"formatter": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"indentStyle": "space",
|
|
27
|
+
"indentWidth": 2,
|
|
28
|
+
"lineWidth": 100
|
|
29
|
+
},
|
|
30
|
+
"javascript": {
|
|
31
|
+
"formatter": {
|
|
32
|
+
"quoteStyle": "single",
|
|
33
|
+
"semicolons": "always"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"json": {
|
|
37
|
+
"formatter": {
|
|
38
|
+
"indentWidth": 2
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
target-version = "py312"
|
|
2
|
+
line-length = 100
|
|
3
|
+
fix = true
|
|
4
|
+
|
|
5
|
+
[lint]
|
|
6
|
+
select = [
|
|
7
|
+
"E", "W", # pycodestyle
|
|
8
|
+
"F", # pyflakes
|
|
9
|
+
"I", # isort
|
|
10
|
+
"N", # pep8-naming
|
|
11
|
+
"UP", # pyupgrade
|
|
12
|
+
"S", # bandit (security)
|
|
13
|
+
"B", # flake8-bugbear
|
|
14
|
+
"A", # flake8-builtins
|
|
15
|
+
"C4", # flake8-comprehensions
|
|
16
|
+
"SIM", # flake8-simplify
|
|
17
|
+
"RUF", # ruff-specific
|
|
18
|
+
]
|
|
19
|
+
ignore = ["E501"] # line length handled by formatter
|
|
20
|
+
|
|
21
|
+
[lint.per-file-ignores]
|
|
22
|
+
"tests/**" = ["S101"] # allow assert in tests
|
|
23
|
+
|
|
24
|
+
[format]
|
|
25
|
+
quote-style = "double"
|
|
26
|
+
indent-style = "space"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
# Usage:
|
|
3
|
+
# semgrep --config auto (recommended community rules)
|
|
4
|
+
# semgrep --config p/owasp-top-ten (OWASP Top 10)
|
|
5
|
+
# semgrep --config p/secrets (secret detection)
|
|
6
|
+
# semgrep --config .semgrep.yml (this file — custom rules)
|
|
7
|
+
|
|
8
|
+
# Custom: ban f-string SQL queries
|
|
9
|
+
- id: no-fstring-sql-execute
|
|
10
|
+
patterns:
|
|
11
|
+
- pattern: cursor.execute($QUERY, ...)
|
|
12
|
+
- pattern-not: cursor.execute("...", ...)
|
|
13
|
+
- metavariable-pattern:
|
|
14
|
+
metavariable: $QUERY
|
|
15
|
+
pattern: |
|
|
16
|
+
f"..."
|
|
17
|
+
message: "Use parameterized queries instead of f-strings in SQL"
|
|
18
|
+
severity: ERROR
|
|
19
|
+
languages: [python]
|
|
20
|
+
|
|
21
|
+
# Custom: no hardcoded secrets in source
|
|
22
|
+
- id: hardcoded-secret-assignment
|
|
23
|
+
pattern-either:
|
|
24
|
+
- pattern: $KEY = "..."
|
|
25
|
+
metavariable-regex:
|
|
26
|
+
metavariable: $KEY
|
|
27
|
+
regex: ".*(secret|password|token|api_key|apikey|auth_token).*"
|
|
28
|
+
message: "Possible hardcoded secret — use environment variables"
|
|
29
|
+
severity: WARNING
|
|
30
|
+
languages: [python, javascript, typescript, java, go, rust]
|
|
31
|
+
|
|
32
|
+
# Custom: no unsafe unwrap in Rust production code
|
|
33
|
+
- id: rust-no-unwrap
|
|
34
|
+
pattern: $X.unwrap()
|
|
35
|
+
message: "Avoid .unwrap() — use .expect() with context or propagate with ?"
|
|
36
|
+
severity: WARNING
|
|
37
|
+
languages: [rust]
|
|
38
|
+
paths:
|
|
39
|
+
exclude:
|
|
40
|
+
- "*_test.rs"
|
|
41
|
+
- "tests/**"
|
|
42
|
+
- "benches/**"
|
|
43
|
+
|
|
44
|
+
# Custom: no fmt.Println in Go production code
|
|
45
|
+
- id: go-no-fmt-println
|
|
46
|
+
pattern: fmt.Println(...)
|
|
47
|
+
message: "Use structured logging instead of fmt.Println"
|
|
48
|
+
severity: WARNING
|
|
49
|
+
languages: [go]
|
|
50
|
+
paths:
|
|
51
|
+
exclude:
|
|
52
|
+
- "*_test.go"
|
|
53
|
+
- "cmd/**"
|
|
54
|
+
|
|
55
|
+
# Paths to exclude from scanning
|
|
56
|
+
paths:
|
|
57
|
+
exclude:
|
|
58
|
+
- node_modules
|
|
59
|
+
- vendor
|
|
60
|
+
- .terraform
|
|
61
|
+
- target
|
|
62
|
+
- dist
|
|
63
|
+
- build
|
|
64
|
+
- "*.min.js"
|
|
65
|
+
- "*.generated.*"
|
|
@@ -30,7 +30,15 @@
|
|
|
30
30
|
{"name": "gitleaks", "feature": "secret-scanning", "install": "brew install gitleaks"},
|
|
31
31
|
{"name": "osv-scanner", "feature": "dep-audit", "install": "go install github.com/google/osv-scanner/cmd/osv-scanner@latest"},
|
|
32
32
|
{"name": "pip-audit", "feature": "dep-audit-python", "install": "pip install pip-audit"},
|
|
33
|
-
{"name": "docker", "feature": "docker-sandbox", "install": "brew install --cask docker"}
|
|
33
|
+
{"name": "docker", "feature": "docker-sandbox", "install": "brew install --cask docker"},
|
|
34
|
+
{"name": "biome", "feature": "lint-js", "install": "npm install -g @biomejs/biome"},
|
|
35
|
+
{"name": "ruff", "feature": "lint-python", "install": "pip install ruff"},
|
|
36
|
+
{"name": "semgrep", "feature": "security-scan", "install": "pip install semgrep"},
|
|
37
|
+
{"name": "markdownlint", "feature": "lint-markdown", "install": "npm install -g markdownlint-cli"},
|
|
38
|
+
{"name": "pre-commit", "feature": "pre-commit-hooks", "install": "pip install pre-commit"},
|
|
39
|
+
{"name": "golangci-lint", "feature": "lint-go", "install": "brew install golangci-lint"},
|
|
40
|
+
{"name": "rustfmt", "feature": "format-rust", "install": "rustup component add rustfmt"},
|
|
41
|
+
{"name": "clippy", "feature": "lint-rust", "install": "rustup component add clippy"}
|
|
34
42
|
]
|
|
35
43
|
},
|
|
36
44
|
"env_vars": {
|
|
@@ -61,6 +61,18 @@ case "$EXT" in
|
|
|
61
61
|
taplo format "$FILE_PATH" 2>/dev/null
|
|
62
62
|
fi
|
|
63
63
|
;;
|
|
64
|
+
js|jsx|ts|tsx)
|
|
65
|
+
if command -v biome &>/dev/null; then
|
|
66
|
+
biome format --write "$FILE_PATH" 2>/dev/null
|
|
67
|
+
elif command -v prettier &>/dev/null; then
|
|
68
|
+
prettier --write "$FILE_PATH" 2>/dev/null
|
|
69
|
+
fi
|
|
70
|
+
;;
|
|
71
|
+
rs)
|
|
72
|
+
if command -v rustfmt &>/dev/null; then
|
|
73
|
+
rustfmt "$FILE_PATH" 2>/dev/null
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
64
76
|
md)
|
|
65
77
|
if command -v prettier &>/dev/null; then
|
|
66
78
|
prettier --write --prose-wrap always "$FILE_PATH" 2>/dev/null
|
|
@@ -111,6 +123,30 @@ case "$EXT" in
|
|
|
111
123
|
fi
|
|
112
124
|
fi
|
|
113
125
|
;;
|
|
126
|
+
js|jsx|ts|tsx)
|
|
127
|
+
if command -v biome &>/dev/null; then
|
|
128
|
+
LINT_OUT=$(biome lint "$FILE_PATH" 2>&1) || true
|
|
129
|
+
if [ -n "$LINT_OUT" ] && ! echo "$LINT_OUT" | grep -q "No diagnostics"; then
|
|
130
|
+
ISSUES+=("biome: $LINT_OUT")
|
|
131
|
+
fi
|
|
132
|
+
fi
|
|
133
|
+
;;
|
|
134
|
+
rs)
|
|
135
|
+
if command -v cargo &>/dev/null; then
|
|
136
|
+
DIR=$(dirname "$FILE_PATH")
|
|
137
|
+
# Walk up to find Cargo.toml for crate context
|
|
138
|
+
CRATE_DIR="$DIR"
|
|
139
|
+
while [ "$CRATE_DIR" != "/" ] && [ ! -f "$CRATE_DIR/Cargo.toml" ]; do
|
|
140
|
+
CRATE_DIR=$(dirname "$CRATE_DIR")
|
|
141
|
+
done
|
|
142
|
+
if [ -f "$CRATE_DIR/Cargo.toml" ]; then
|
|
143
|
+
LINT_OUT=$(cd "$CRATE_DIR" && cargo clippy --quiet -- -D warnings 2>&1) || true
|
|
144
|
+
if [ -n "$LINT_OUT" ]; then
|
|
145
|
+
ISSUES+=("clippy: $LINT_OUT")
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
;;
|
|
114
150
|
yml|yaml)
|
|
115
151
|
if command -v yamllint &>/dev/null; then
|
|
116
152
|
LINT_OUT=$(yamllint -f parsable "$FILE_PATH" 2>&1) || true
|
package/package.json
CHANGED