@pipemd-core/pipemd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_SETUP_PIPEMD.md +184 -0
- package/CHANGELOG.md +47 -0
- package/LICENSE +15 -0
- package/README.md +535 -0
- package/dist/index.js +6647 -0
- package/dist/plugins/opencode-server.js +235 -0
- package/dist/plugins/opencode-tui.js +914 -0
- package/dist/templates/agent-decision-tree.md +113 -0
- package/dist/templates/static-rules.md +7 -0
- package/package.json +68 -0
- package/scripts/C-CPP/architecture/arch.sh +229 -0
- package/scripts/C-CPP/lib/limit.sh +146 -0
- package/scripts/C-CPP/project/class-diagram.sh +96 -0
- package/scripts/C-CPP/project/cmake-targets.sh +68 -0
- package/scripts/C-CPP/project/deps.sh +44 -0
- package/scripts/C-CPP/project/find-todos.sh +6 -0
- package/scripts/C-CPP/project/include-graph.sh +110 -0
- package/scripts/C-CPP/project/interfaces.sh +108 -0
- package/scripts/C-CPP/project/tree.sh +5 -0
- package/scripts/C-CPP/quality/lint.sh +14 -0
- package/scripts/C-CPP/quality/test-summary.sh +22 -0
- package/scripts/C-CPP/quality/type-check.sh +26 -0
- package/scripts/DevOps/architecture/arch.sh +186 -0
- package/scripts/DevOps/devops/aws-context.sh +34 -0
- package/scripts/DevOps/devops/docker-stats.sh +42 -0
- package/scripts/DevOps/devops/k8s-unhealthy.sh +41 -0
- package/scripts/DevOps/devops/tf-state.sh +65 -0
- package/scripts/DevOps/lib/limit.sh +143 -0
- package/scripts/Generic/architecture/arch.sh +570 -0
- package/scripts/Generic/lib/limit.sh +140 -0
- package/scripts/Go/architecture/arch.sh +79 -0
- package/scripts/Go/lib/limit.sh +142 -0
- package/scripts/Go/project/deps.sh +35 -0
- package/scripts/Go/project/find-todos.sh +6 -0
- package/scripts/Go/project/go-interfaces.sh +18 -0
- package/scripts/Go/project/go-packages.sh +28 -0
- package/scripts/Go/project/tree.sh +5 -0
- package/scripts/Go/quality/lint.sh +16 -0
- package/scripts/Go/quality/test-summary.sh +16 -0
- package/scripts/Go/quality/type-check.sh +16 -0
- package/scripts/Node-TypeScript/api/express-routes.sh +14 -0
- package/scripts/Node-TypeScript/api/nest-controllers.sh +18 -0
- package/scripts/Node-TypeScript/architecture/arch.sh +174 -0
- package/scripts/Node-TypeScript/frontend/angular-routes.sh +15 -0
- package/scripts/Node-TypeScript/frontend/nextjs-app-router.sh +13 -0
- package/scripts/Node-TypeScript/frontend/react-components.sh +20 -0
- package/scripts/Node-TypeScript/lib/limit.sh +146 -0
- package/scripts/Node-TypeScript/project/deps.sh +15 -0
- package/scripts/Node-TypeScript/project/find-todos.sh +6 -0
- package/scripts/Node-TypeScript/quality/lint.sh +10 -0
- package/scripts/Node-TypeScript/quality/test-summary.sh +39 -0
- package/scripts/Node-TypeScript/quality/type-check.sh +10 -0
- package/scripts/Python/api/fastapi-routes.sh +12 -0
- package/scripts/Python/architecture/arch.sh +220 -0
- package/scripts/Python/db/django-models.sh +12 -0
- package/scripts/Python/db/sqlalchemy.sh +17 -0
- package/scripts/Python/lib/limit.sh +144 -0
- package/scripts/Python/project/deps.sh +28 -0
- package/scripts/Python/project/find-todos.sh +6 -0
- package/scripts/Python/quality/lint.sh +13 -0
- package/scripts/Python/quality/test-summary.sh +11 -0
- package/scripts/Python/quality/type-check.sh +10 -0
- package/scripts/Rust/architecture/arch.sh +176 -0
- package/scripts/Rust/lib/limit.sh +142 -0
- package/scripts/Rust/project/cargo-deps.sh +42 -0
- package/scripts/Rust/project/cargo-features.sh +26 -0
- package/scripts/Rust/project/find-todos.sh +6 -0
- package/scripts/Rust/project/tree.sh +5 -0
- package/scripts/Rust/quality/lint.sh +16 -0
- package/scripts/Rust/quality/test-summary.sh +16 -0
- package/scripts/Rust/quality/type-check.sh +16 -0
- package/scripts/Shared/api/express-routes.sh +11 -0
- package/scripts/Shared/api/fastapi-routes.sh +10 -0
- package/scripts/Shared/api/nest-controllers.sh +22 -0
- package/scripts/Shared/architecture/normalize.sh +178 -0
- package/scripts/Shared/crew/crew.sh +15 -0
- package/scripts/Shared/db/django-models.sh +11 -0
- package/scripts/Shared/db/prisma.sh +33 -0
- package/scripts/Shared/db/sqlalchemy.sh +12 -0
- package/scripts/Shared/frontend/angular-routes.sh +11 -0
- package/scripts/Shared/frontend/nextjs-app-router.sh +13 -0
- package/scripts/Shared/frontend/react-components.sh +11 -0
- package/scripts/Shared/git/diff-stat.sh +6 -0
- package/scripts/Shared/git/git-branch.sh +16 -0
- package/scripts/Shared/git/git-log.sh +6 -0
- package/scripts/Shared/git/git-status.sh +6 -0
- package/scripts/Shared/lib/limit.sh +144 -0
- package/scripts/Shared/project/compose-md.sh +182 -0
- package/scripts/Shared/project/deps.sh +69 -0
- package/scripts/Shared/project/find-todos.sh +6 -0
- package/scripts/Shared/project/tree.sh +5 -0
- package/scripts/Shared/quality/lint.sh +81 -0
- package/scripts/Shared/quality/test-summary.sh +103 -0
- package/scripts/Shared/quality/type-check.sh +114 -0
- package/scripts/copy-plugins.mjs +4 -0
- package/scripts/copy-templates.mjs +5 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Architecture map — Python module dependencies
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
|
|
6
|
+
: "${MAX_ARCH:=100}"
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
NORMALIZE="$SCRIPT_DIR/normalize.sh"
|
|
10
|
+
[ -f "$NORMALIZE" ] || NORMALIZE="$SCRIPT_DIR/../../Shared/architecture/normalize.sh"
|
|
11
|
+
|
|
12
|
+
if ! command -v python3 &>/dev/null; then
|
|
13
|
+
echo "python3 is required for Python architecture extraction"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
SRC_DIR=""
|
|
18
|
+
for dir in src app lib .; do
|
|
19
|
+
if [ -d "$dir" ]; then
|
|
20
|
+
has_py=$(find "$dir" -maxdepth 3 -name "*.py" -not -path "*/__pycache__/*" -print -quit 2>/dev/null)
|
|
21
|
+
if [ -n "$has_py" ]; then
|
|
22
|
+
SRC_DIR="$dir"
|
|
23
|
+
break
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
done
|
|
27
|
+
[ -z "$SRC_DIR" ] && { echo "No source directory found"; exit 0; }
|
|
28
|
+
SRC_DIR="$(cd "$SRC_DIR" && pwd)"
|
|
29
|
+
|
|
30
|
+
SRC_DIR_ABS="$SRC_DIR" python3 -c "
|
|
31
|
+
import os, re, ast, sys
|
|
32
|
+
from collections import defaultdict
|
|
33
|
+
|
|
34
|
+
SRC_DIR = os.environ.get('SRC_DIR_ABS', 'src')
|
|
35
|
+
MAX_MODULES = 40
|
|
36
|
+
|
|
37
|
+
SKIP_DIRS = {'__pycache__', 'venv', '.venv', '.mypy_cache', '.tox', 'dist', 'build',
|
|
38
|
+
'.eggs', '.pytest_cache', 'node_modules', '.git', '.pipemd', 'migrations'}
|
|
39
|
+
|
|
40
|
+
def module_name(rel_path):
|
|
41
|
+
rel_path = rel_path.replace(os.sep, '/')
|
|
42
|
+
parts = rel_path.split('/')
|
|
43
|
+
filename = os.path.splitext(parts[-1])[0] if parts else ''
|
|
44
|
+
dirpath = parts[:-1] if len(parts) > 1 else []
|
|
45
|
+
|
|
46
|
+
if filename == '__init__':
|
|
47
|
+
return '/'.join(dirpath) if dirpath else None
|
|
48
|
+
if not dirpath:
|
|
49
|
+
return filename
|
|
50
|
+
return '/'.join(dirpath) + '/' + filename
|
|
51
|
+
|
|
52
|
+
def collect_pyproject_deps():
|
|
53
|
+
deps = set()
|
|
54
|
+
for pkgfile in ('requirements.txt', 'pyproject.toml', 'Pipfile', 'setup.py', 'setup.cfg'):
|
|
55
|
+
try:
|
|
56
|
+
with open(pkgfile, 'r', errors='replace') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
except Exception:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
if pkgfile == 'requirements.txt':
|
|
62
|
+
for line in content.split('\n'):
|
|
63
|
+
line = line.strip()
|
|
64
|
+
if not line or line.startswith('#') or line.startswith('-'):
|
|
65
|
+
continue
|
|
66
|
+
name = re.split(r'[<>=!;\[]', line)[0].strip().lower()
|
|
67
|
+
if name:
|
|
68
|
+
deps.add(name)
|
|
69
|
+
|
|
70
|
+
elif pkgfile == 'pyproject.toml':
|
|
71
|
+
m = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
|
|
72
|
+
if m:
|
|
73
|
+
for item in re.findall(r'[\"\'](.*?)[\"\']', m.group(1)):
|
|
74
|
+
name = re.split(r'[<>=!;\[]', item)[0].strip().lower()
|
|
75
|
+
if name:
|
|
76
|
+
deps.add(name)
|
|
77
|
+
m2 = re.search(r'\[project\.dependencies\](.*?)(?=\[|\Z)', content, re.DOTALL)
|
|
78
|
+
if m2:
|
|
79
|
+
for line in m2.group(1).strip().split('\n'):
|
|
80
|
+
line = line.strip()
|
|
81
|
+
if line and line[0].isalpha():
|
|
82
|
+
name = re.split(r'[<>=!;\[]', line)[0].strip().lower()
|
|
83
|
+
if name:
|
|
84
|
+
deps.add(name)
|
|
85
|
+
|
|
86
|
+
elif pkgfile == 'Pipfile':
|
|
87
|
+
in_packages = False
|
|
88
|
+
for line in content.split('\n'):
|
|
89
|
+
stripped = line.strip()
|
|
90
|
+
if stripped == '[packages]':
|
|
91
|
+
in_packages = True
|
|
92
|
+
continue
|
|
93
|
+
if stripped.startswith('['):
|
|
94
|
+
in_packages = False
|
|
95
|
+
continue
|
|
96
|
+
if in_packages and stripped and stripped[0].isalpha():
|
|
97
|
+
name = re.split(r'[=<>#\s]', stripped)[0].strip().lower()
|
|
98
|
+
if name:
|
|
99
|
+
deps.add(name)
|
|
100
|
+
|
|
101
|
+
elif pkgfile in ('setup.py', 'setup.cfg'):
|
|
102
|
+
for m_pat in re.finditer(r'(?:install_requires|dependencies)\s*=\s*\[(.*?)\]', content, re.DOTALL):
|
|
103
|
+
for item in re.findall(r'[\"\'](.*?)[\"\']', m_pat.group(1)):
|
|
104
|
+
name = re.split(r'[<>=!;\[]', item)[0].strip().lower()
|
|
105
|
+
if name:
|
|
106
|
+
deps.add(name)
|
|
107
|
+
|
|
108
|
+
return deps
|
|
109
|
+
|
|
110
|
+
ext_deps = collect_pyproject_deps()
|
|
111
|
+
|
|
112
|
+
files = []
|
|
113
|
+
for root, dirs, fnames in os.walk(SRC_DIR):
|
|
114
|
+
dirs[:] = [d for d in sorted(dirs) if d not in SKIP_DIRS and not d.startswith('.')]
|
|
115
|
+
for fn in sorted(fnames):
|
|
116
|
+
if fn.endswith('.py') and not fn.startswith('.'):
|
|
117
|
+
files.append(os.path.join(root, fn))
|
|
118
|
+
if len(files) >= 300:
|
|
119
|
+
files = files[:300]
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
if not files:
|
|
123
|
+
sys.exit(0)
|
|
124
|
+
|
|
125
|
+
module_set = set()
|
|
126
|
+
for fpath in files:
|
|
127
|
+
rel = os.path.relpath(fpath, SRC_DIR).replace(os.sep, '/')
|
|
128
|
+
mod = module_name(rel)
|
|
129
|
+
if mod:
|
|
130
|
+
module_set.add(mod)
|
|
131
|
+
|
|
132
|
+
if len(module_set) > MAX_MODULES:
|
|
133
|
+
module_set = set()
|
|
134
|
+
for fpath in files:
|
|
135
|
+
rel = os.path.relpath(fpath, SRC_DIR).replace(os.sep, '/')
|
|
136
|
+
mod = module_name(rel)
|
|
137
|
+
if mod:
|
|
138
|
+
parts = mod.split('/')
|
|
139
|
+
module_set.add(parts[0] if len(parts) > 1 else parts[0])
|
|
140
|
+
|
|
141
|
+
def top_pkg(name):
|
|
142
|
+
return name.split('.')[0].split('/')[0].lower()
|
|
143
|
+
|
|
144
|
+
def resolve_internal(import_path):
|
|
145
|
+
check = import_path.replace('.', '/')
|
|
146
|
+
for mod in module_set:
|
|
147
|
+
if mod == check or mod.startswith(check + '/'):
|
|
148
|
+
return check.split('/')[0]
|
|
149
|
+
check_dir = os.path.join(SRC_DIR, check)
|
|
150
|
+
if os.path.isdir(check_dir):
|
|
151
|
+
return import_path.split('.')[0].split('/')[0].lower()
|
|
152
|
+
check_file = os.path.join(SRC_DIR, check + '.py')
|
|
153
|
+
if os.path.isfile(check_file):
|
|
154
|
+
return import_path.split('.')[0].split('/')[0].lower()
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
edges = set()
|
|
158
|
+
|
|
159
|
+
for fpath in files:
|
|
160
|
+
rel = os.path.relpath(fpath, SRC_DIR).replace(os.sep, '/')
|
|
161
|
+
src_mod = module_name(rel)
|
|
162
|
+
if not src_mod or src_mod not in module_set:
|
|
163
|
+
continue
|
|
164
|
+
src_short = src_mod.split('/')[0] if '/' in src_mod else src_mod
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
with open(fpath, 'r', errors='replace') as f:
|
|
168
|
+
content = f.read()
|
|
169
|
+
except Exception:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
tree = ast.parse(content)
|
|
174
|
+
except SyntaxError:
|
|
175
|
+
for line in content.split('\n'):
|
|
176
|
+
line = line.strip()
|
|
177
|
+
if line.startswith('import ') or line.startswith('from '):
|
|
178
|
+
pass
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
for node in ast.walk(tree):
|
|
182
|
+
if isinstance(node, ast.Import):
|
|
183
|
+
for alias in node.names:
|
|
184
|
+
pkg = alias.name.split('.')[0].lower()
|
|
185
|
+
if pkg in {m.split('/')[0].split('.')[0] for m in module_set if '/' not in m and '.' not in m} or \
|
|
186
|
+
pkg in {m.split('/')[0] for m in module_set if '/' in m}:
|
|
187
|
+
internal = resolve_internal(alias.name)
|
|
188
|
+
if internal and internal != src_short:
|
|
189
|
+
edges.add((src_short, internal))
|
|
190
|
+
elif pkg in ext_deps:
|
|
191
|
+
edges.add((src_short, 'ext:' + pkg))
|
|
192
|
+
|
|
193
|
+
if isinstance(node, ast.ImportFrom):
|
|
194
|
+
if node.level and node.level > 0:
|
|
195
|
+
file_dir = os.path.dirname(rel) if '/' in rel else ''
|
|
196
|
+
parts = file_dir.split('/') if file_dir else []
|
|
197
|
+
go_up = node.level - 1
|
|
198
|
+
if go_up > len(parts):
|
|
199
|
+
go_up = len(parts)
|
|
200
|
+
base_parts = parts[:len(parts) - go_up] if go_up > 0 else list(parts)
|
|
201
|
+
if node.module:
|
|
202
|
+
base_parts = base_parts + node.module.split('.')
|
|
203
|
+
target = '/'.join(base_parts) if base_parts else file_dir
|
|
204
|
+
if target:
|
|
205
|
+
target_short = target.split('/')[0] if '/' in target else target
|
|
206
|
+
if target_short and target_short != src_short:
|
|
207
|
+
edges.add((src_short, target_short))
|
|
208
|
+
elif node.module:
|
|
209
|
+
top = node.module.split('.')[0].lower()
|
|
210
|
+
if top in {m.split('/')[0].split('.')[0] for m in module_set if '/' not in m and '.' not in m} or \
|
|
211
|
+
top in {m.split('/')[0] for m in module_set if '/' in m}:
|
|
212
|
+
internal = resolve_internal(node.module)
|
|
213
|
+
if internal and internal != src_short:
|
|
214
|
+
edges.add((src_short, internal))
|
|
215
|
+
elif top in ext_deps:
|
|
216
|
+
edges.add((src_short, 'ext:' + top))
|
|
217
|
+
|
|
218
|
+
for s, d in sorted(edges):
|
|
219
|
+
sys.stdout.write(s + '\t' + d + '\n')
|
|
220
|
+
" | MAX_ARCH="$MAX_ARCH" bash "$NORMALIZE"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Django model metadata — model names and field summaries
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
files=$(find . -name 'models.py' -not -path '*/venv/*' -not -path '*/.venv/*' -not -path '*/node_modules/*' -not -path '*/__pycache__/*' 2>/dev/null | head -10)
|
|
6
|
+
[ -z "$files" ] && echo "No Django models.py found" && exit 0
|
|
7
|
+
|
|
8
|
+
echo "$files" | while IFS= read -r f; do
|
|
9
|
+
[ -z "$f" ] && continue
|
|
10
|
+
echo "=== $f ==="
|
|
11
|
+
awk '/^class [A-Z]/{gsub(/\(.*/,"",$0); print "Model: "$2; next} /^[[:space:]]+[a-z_]+ = models\./{gsub(/ = .*/,"",$1); print " "$1; next}' "$f" | head -"$MAX_DJANGO"
|
|
12
|
+
done | head -50
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# SQLAlchemy model metadata — model class names and table names
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
files=$(find . -name '*.py' -not -path '*/venv/*' -not -path '*/.venv/*' -not -path '*/node_modules/*' -not -path '*/__pycache__/*' 2>/dev/null | head -20)
|
|
6
|
+
[ -z "$files" ] && echo "No Python files found" && exit 0
|
|
7
|
+
|
|
8
|
+
grep -rn --include='*.py' -E 'class [A-Z][A-Za-z0-9_]*\(.*Base.*\)|__tablename__' . 2>/dev/null \
|
|
9
|
+
| grep -v 'venv' | grep -v '.venv' | grep -v '__pycache__' \
|
|
10
|
+
| sed -E 's/.*:class ([A-Z][A-Za-z0-9_]*)\(.*/\1/' \
|
|
11
|
+
| grep -E '^[A-Z]' | sort -u | head -"$MAX_SQLALCHEMY"
|
|
12
|
+
|
|
13
|
+
tablenames=$(grep -rn --include='*.py' -E '__tablename__' . 2>/dev/null \
|
|
14
|
+
| grep -v 'venv' | grep -v '__pycache__' \
|
|
15
|
+
| sed -E "s/.*__tablename__[[:space:]]*=[[:space:]]*['\"]([^'\"]*)['\"].*/\1/" \
|
|
16
|
+
| sort -u | head -10)
|
|
17
|
+
[ -n "$tablenames" ] && echo "" && echo "Tables:" && echo "$tablenames"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# limit.sh — Smart output limiter with compact fallbacks
|
|
4
|
+
# TOKEN profile multiplier (override via PMD_TOKEN_PROFILE env var)
|
|
5
|
+
PMD_TOKEN_PROFILE="${PMD_TOKEN_PROFILE:-medium}"
|
|
6
|
+
case "$PMD_TOKEN_PROFILE" in
|
|
7
|
+
low) MULT_NUM=1; MULT_DEN=2 ;;
|
|
8
|
+
medium) MULT_NUM=1; MULT_DEN=1 ;;
|
|
9
|
+
high) MULT_NUM=3; MULT_DEN=2 ;;
|
|
10
|
+
xhigh) MULT_NUM=2; MULT_DEN=1 ;;
|
|
11
|
+
unlimited) PMD_UNLIMITED=1 ;;
|
|
12
|
+
*) MULT_NUM=1; MULT_DEN=1 ;;
|
|
13
|
+
esac
|
|
14
|
+
|
|
15
|
+
# Token budget constants (override via PMD_MAX_* env vars, scaled by profile)
|
|
16
|
+
if [ "${PMD_UNLIMITED:-0}" = "1" ]; then
|
|
17
|
+
MAX_TREE=${PMD_MAX_TREE:-99999}
|
|
18
|
+
MAX_DEPS=${PMD_MAX_DEPS:-99999}
|
|
19
|
+
MAX_TODOS=${PMD_MAX_TODOS:-99999}
|
|
20
|
+
MAX_LOG=${PMD_MAX_LOG:-99999}
|
|
21
|
+
MAX_BRANCH=${PMD_MAX_BRANCH:-99999}
|
|
22
|
+
MAX_STATUS=${PMD_MAX_STATUS:-99999}
|
|
23
|
+
MAX_DIFF=${PMD_MAX_DIFF:-99999}
|
|
24
|
+
MAX_TYPECHECK=${PMD_MAX_TYPECHECK:-99999}
|
|
25
|
+
MAX_LINT=${PMD_MAX_LINT:-99999}
|
|
26
|
+
MAX_TEST=${PMD_MAX_TEST:-99999}
|
|
27
|
+
MAX_PRISMA=${PMD_MAX_PRISMA:-99999}
|
|
28
|
+
MAX_EXPRESS=${PMD_MAX_EXPRESS:-99999}
|
|
29
|
+
MAX_FASTAPI=${PMD_MAX_FASTAPI:-99999}
|
|
30
|
+
MAX_DJANGO=${PMD_MAX_DJANGO:-99999}
|
|
31
|
+
MAX_SQLALCHEMY=${PMD_MAX_SQLALCHEMY:-99999}
|
|
32
|
+
MAX_NEST=${PMD_MAX_NEST:-99999}
|
|
33
|
+
MAX_NEXTJS=${PMD_MAX_NEXTJS:-99999}
|
|
34
|
+
MAX_REACT=${PMD_MAX_REACT:-99999}
|
|
35
|
+
MAX_ANGULAR=${PMD_MAX_ANGULAR:-99999}
|
|
36
|
+
MAX_CMAKE=${PMD_MAX_CMAKE:-99999}
|
|
37
|
+
MAX_CLASS=${PMD_MAX_CLASS:-99999}
|
|
38
|
+
MAX_INTERFACE=${PMD_MAX_INTERFACE:-99999}
|
|
39
|
+
MAX_INCLUDE=${PMD_MAX_INCLUDE:-99999}
|
|
40
|
+
MAX_CARGO=${PMD_MAX_CARGO:-99999}
|
|
41
|
+
MAX_GO_PKGS=${PMD_MAX_GO_PKGS:-99999}
|
|
42
|
+
MAX_CARGO_FEATURES=${PMD_MAX_CARGO_FEATURES:-99999}
|
|
43
|
+
MAX_GO_INTERFACES=${PMD_MAX_GO_INTERFACES:-99999}
|
|
44
|
+
MAX_DOCKER=${PMD_MAX_DOCKER:-99999}
|
|
45
|
+
MAX_K8S=${PMD_MAX_K8S:-99999}
|
|
46
|
+
MAX_TF=${PMD_MAX_TF:-99999}
|
|
47
|
+
MAX_AWS=${PMD_MAX_AWS:-99999}
|
|
48
|
+
MAX_ARCH=${PMD_MAX_ARCH:-99999}
|
|
49
|
+
MAX_COMPOSE=${PMD_MAX_COMPOSE:-99999}
|
|
50
|
+
MAX_CREW=${PMD_MAX_CREW:-99999}
|
|
51
|
+
else
|
|
52
|
+
MAX_TREE=$(( (${PMD_MAX_TREE:-50} * MULT_NUM) / MULT_DEN ))
|
|
53
|
+
MAX_DEPS=$(( (${PMD_MAX_DEPS:-40} * MULT_NUM) / MULT_DEN ))
|
|
54
|
+
MAX_TODOS=$(( (${PMD_MAX_TODOS:-20} * MULT_NUM) / MULT_DEN ))
|
|
55
|
+
MAX_LOG=$(( (${PMD_MAX_LOG:-20} * MULT_NUM) / MULT_DEN ))
|
|
56
|
+
MAX_BRANCH=$(( (${PMD_MAX_BRANCH:-20} * MULT_NUM) / MULT_DEN ))
|
|
57
|
+
MAX_STATUS=$(( (${PMD_MAX_STATUS:-30} * MULT_NUM) / MULT_DEN ))
|
|
58
|
+
MAX_DIFF=$(( (${PMD_MAX_DIFF:-30} * MULT_NUM) / MULT_DEN ))
|
|
59
|
+
MAX_TYPECHECK=$(( (${PMD_MAX_TYPECHECK:-30} * MULT_NUM) / MULT_DEN ))
|
|
60
|
+
MAX_LINT=$(( (${PMD_MAX_LINT:-20} * MULT_NUM) / MULT_DEN ))
|
|
61
|
+
MAX_TEST=$(( (${PMD_MAX_TEST:-10} * MULT_NUM) / MULT_DEN ))
|
|
62
|
+
MAX_PRISMA=$(( (${PMD_MAX_PRISMA:-40} * MULT_NUM) / MULT_DEN ))
|
|
63
|
+
MAX_EXPRESS=$(( (${PMD_MAX_EXPRESS:-30} * MULT_NUM) / MULT_DEN ))
|
|
64
|
+
MAX_FASTAPI=$(( (${PMD_MAX_FASTAPI:-30} * MULT_NUM) / MULT_DEN ))
|
|
65
|
+
MAX_DJANGO=$(( (${PMD_MAX_DJANGO:-40} * MULT_NUM) / MULT_DEN ))
|
|
66
|
+
MAX_SQLALCHEMY=$(( (${PMD_MAX_SQLALCHEMY:-40} * MULT_NUM) / MULT_DEN ))
|
|
67
|
+
MAX_NEST=$(( (${PMD_MAX_NEST:-30} * MULT_NUM) / MULT_DEN ))
|
|
68
|
+
MAX_NEXTJS=$(( (${PMD_MAX_NEXTJS:-30} * MULT_NUM) / MULT_DEN ))
|
|
69
|
+
MAX_REACT=$(( (${PMD_MAX_REACT:-30} * MULT_NUM) / MULT_DEN ))
|
|
70
|
+
MAX_ANGULAR=$(( (${PMD_MAX_ANGULAR:-30} * MULT_NUM) / MULT_DEN ))
|
|
71
|
+
MAX_CMAKE=$(( (${PMD_MAX_CMAKE:-40} * MULT_NUM) / MULT_DEN ))
|
|
72
|
+
MAX_CLASS=$(( (${PMD_MAX_CLASS:-40} * MULT_NUM) / MULT_DEN ))
|
|
73
|
+
MAX_INTERFACE=$(( (${PMD_MAX_INTERFACE:-30} * MULT_NUM) / MULT_DEN ))
|
|
74
|
+
MAX_INCLUDE=$(( (${PMD_MAX_INCLUDE:-40} * MULT_NUM) / MULT_DEN ))
|
|
75
|
+
MAX_CARGO=$(( (${PMD_MAX_CARGO:-40} * MULT_NUM) / MULT_DEN ))
|
|
76
|
+
MAX_GO_PKGS=$(( (${PMD_MAX_GO_PKGS:-40} * MULT_NUM) / MULT_DEN ))
|
|
77
|
+
MAX_CARGO_FEATURES=$(( (${PMD_MAX_CARGO_FEATURES:-20} * MULT_NUM) / MULT_DEN ))
|
|
78
|
+
MAX_GO_INTERFACES=$(( (${PMD_MAX_GO_INTERFACES:-30} * MULT_NUM) / MULT_DEN ))
|
|
79
|
+
MAX_DOCKER=$(( (${PMD_MAX_DOCKER:-30} * MULT_NUM) / MULT_DEN ))
|
|
80
|
+
MAX_K8S=$(( (${PMD_MAX_K8S:-20} * MULT_NUM) / MULT_DEN ))
|
|
81
|
+
MAX_TF=$(( (${PMD_MAX_TF:-40} * MULT_NUM) / MULT_DEN ))
|
|
82
|
+
MAX_AWS=$(( (${PMD_MAX_AWS:-10} * MULT_NUM) / MULT_DEN ))
|
|
83
|
+
MAX_ARCH=$(( (${PMD_MAX_ARCH:-100} * MULT_NUM) / MULT_DEN ))
|
|
84
|
+
MAX_COMPOSE=$(( (${PMD_MAX_COMPOSE:-150} * MULT_NUM) / MULT_DEN ))
|
|
85
|
+
MAX_CREW=$(( (${PMD_MAX_CREW:-40} * MULT_NUM) / MULT_DEN ))
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
TREE_EXCLUDES="${PMD_TREE_EXCLUDES:-__pycache__|.git|.pipemd|venv|.venv|*.egg-info|node_modules|dist|build}"
|
|
89
|
+
|
|
90
|
+
limit_output() {
|
|
91
|
+
local text="$1"
|
|
92
|
+
local max="${2:-25}"
|
|
93
|
+
local fallback="$3"
|
|
94
|
+
local lines
|
|
95
|
+
lines=$(echo "$text" | wc -l)
|
|
96
|
+
if [ "$lines" -le "$max" ]; then
|
|
97
|
+
echo "$text"
|
|
98
|
+
else
|
|
99
|
+
echo "$fallback"
|
|
100
|
+
fi
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
limit_tree() {
|
|
104
|
+
local max="${1:-$MAX_TREE}"
|
|
105
|
+
local excl="${TREE_EXCLUDES}"
|
|
106
|
+
|
|
107
|
+
if command -v tree &>/dev/null; then
|
|
108
|
+
local out3
|
|
109
|
+
out3=$(tree -L 3 -I "$excl" --dirsfirst 2>/dev/null)
|
|
110
|
+
local lines3=$(echo "$out3" | wc -l)
|
|
111
|
+
|
|
112
|
+
if [ "$lines3" -le "$max" ]; then
|
|
113
|
+
echo "$out3"
|
|
114
|
+
return
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
local out2
|
|
118
|
+
out2=$(tree -L 2 -I "$excl" --dirsfirst 2>/dev/null)
|
|
119
|
+
local lines2=$(echo "$out2" | wc -l)
|
|
120
|
+
|
|
121
|
+
if [ "$lines2" -le "$max" ]; then
|
|
122
|
+
echo "$out2"
|
|
123
|
+
echo "(${lines3} lines at depth 3, showing depth 2)"
|
|
124
|
+
return
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
local out1
|
|
128
|
+
out1=$(tree -L 1 -I "$excl" --dirsfirst 2>/dev/null)
|
|
129
|
+
echo "$out1"
|
|
130
|
+
echo "(${lines3} lines at depth 3, showing depth 1)"
|
|
131
|
+
else
|
|
132
|
+
echo "Project structure:"
|
|
133
|
+
find . -maxdepth 3 \
|
|
134
|
+
-not -path '*/__pycache__/*' \
|
|
135
|
+
-not -path '*/.git/*' \
|
|
136
|
+
-not -path '*/.pipemd/*' \
|
|
137
|
+
-not -path '*/venv/*' \
|
|
138
|
+
-not -path '*/.venv/*' \
|
|
139
|
+
-not -name '__pycache__' \
|
|
140
|
+
-not -name '.git' \
|
|
141
|
+
-not -name '.pipemd' \
|
|
142
|
+
2>/dev/null | head -"$max" | sort
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Direct dependencies — summarize if too many
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
|
|
6
|
+
if [ -f requirements.txt ]; then
|
|
7
|
+
out=$(head -20 requirements.txt)
|
|
8
|
+
limit_output "$out" "$MAX_DEPS" "$(head -5 requirements.txt && echo "... and $(wc -l < requirements.txt) total lines")"
|
|
9
|
+
elif [ -f pyproject.toml ]; then
|
|
10
|
+
out=$(awk '
|
|
11
|
+
/^\[project\.dependencies\]/{in_deps=1;next}
|
|
12
|
+
/^\[/{in_deps=0}
|
|
13
|
+
in_deps && /^[a-zA-Z]/{
|
|
14
|
+
gsub(/#.*/,"")
|
|
15
|
+
gsub(/;.*$/,"")
|
|
16
|
+
gsub(/"/,"")
|
|
17
|
+
sub(/[<=>!].*/,"")
|
|
18
|
+
if(length>0) print
|
|
19
|
+
}
|
|
20
|
+
' pyproject.toml 2>/dev/null)
|
|
21
|
+
if [ -n "$out" ]; then
|
|
22
|
+
limit_output "$out" "$MAX_DEPS" "$(echo "$out" | head -5 && echo '... more in pyproject.toml')"
|
|
23
|
+
else
|
|
24
|
+
echo "No dependencies found in pyproject.toml"
|
|
25
|
+
fi
|
|
26
|
+
else
|
|
27
|
+
echo "No recognized dependency file"
|
|
28
|
+
fi
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Find TODO, FIXME, HACK — Python files
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
out=$(grep -rn 'TODO\|FIXME\|HACK' --include="*.py" . 2>/dev/null | grep -v '/__pycache__/' | grep -v '/.pipemd/' | grep -v '/venv/' | grep -v '/.venv/' | grep -v '/.git/')
|
|
6
|
+
limit_output "$out" "$MAX_TODOS" "$(echo "$out" | head -3 && echo "... and $(($(echo "$out" | wc -l) - 3)) more items")"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Lint errors — ruff or flake8
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
if command -v ruff &>/dev/null; then
|
|
6
|
+
out=$(ruff check . 2>&1)
|
|
7
|
+
limit_output "$out" "$MAX_LINT" "$(echo "$out" | head -3 && echo "... and $(echo "$out" | wc -l) more lint issues")"
|
|
8
|
+
elif command -v flake8 &>/dev/null; then
|
|
9
|
+
out=$(flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 2>&1)
|
|
10
|
+
limit_output "$out" "$MAX_LINT" "$(echo "$out" | head -3 && echo '... more flake8 errors')"
|
|
11
|
+
else
|
|
12
|
+
echo "No linter configured"
|
|
13
|
+
fi
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Test summary — actually run tests and show results
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
|
|
6
|
+
if command -v pytest &>/dev/null || [ -f pytest.ini ] || [ -f conftest.py ] || [ -f pyproject.toml ]; then
|
|
7
|
+
out=$(python -m pytest --tb=no -q 2>&1 | tail -5)
|
|
8
|
+
echo "$out"
|
|
9
|
+
else
|
|
10
|
+
echo "No test runner configured for this ecosystem"
|
|
11
|
+
fi
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Python type check (mypy) — errors only
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
out=$(python -m mypy . 2>&1)
|
|
6
|
+
if [ -z "$out" ]; then
|
|
7
|
+
echo "No type errors"
|
|
8
|
+
exit 0
|
|
9
|
+
fi
|
|
10
|
+
limit_output "$out" "$MAX_TYPECHECK" "$(echo "$out" | head -5 && echo '... more mypy errors')"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Architecture map — Rust module dependencies
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
|
|
6
|
+
: "${MAX_ARCH:=100}"
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
NORMALIZE="$SCRIPT_DIR/normalize.sh"
|
|
10
|
+
[ -f "$NORMALIZE" ] || NORMALIZE="$SCRIPT_DIR/../../Shared/architecture/normalize.sh"
|
|
11
|
+
|
|
12
|
+
if [ ! -f Cargo.toml ]; then
|
|
13
|
+
echo "No Cargo.toml found"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if ! command -v python3 &>/dev/null; then
|
|
18
|
+
echo "python3 is required for Rust architecture extraction"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
SRC_DIR=""
|
|
23
|
+
[ -d "src" ] && SRC_DIR="src"
|
|
24
|
+
|
|
25
|
+
if [ -z "$SRC_DIR" ]; then
|
|
26
|
+
echo "No source directory found"
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
SRC_DIR="$SRC_DIR" python3 -c "
|
|
31
|
+
import os, re, sys
|
|
32
|
+
from collections import defaultdict
|
|
33
|
+
|
|
34
|
+
SRC_DIR = os.environ.get('SRC_DIR', 'src')
|
|
35
|
+
MAX_MODULES = 40
|
|
36
|
+
|
|
37
|
+
def module_name(rel_path):
|
|
38
|
+
rel_path = rel_path.replace(os.sep, '/')
|
|
39
|
+
parts = rel_path.split('/')
|
|
40
|
+
filename = os.path.splitext(parts[-1])[0] if parts else ''
|
|
41
|
+
dirpath = parts[:-1] if len(parts) > 1 else []
|
|
42
|
+
|
|
43
|
+
if not dirpath:
|
|
44
|
+
return filename
|
|
45
|
+
|
|
46
|
+
if len(dirpath) == 1 and dirpath[0] == SRC_DIR:
|
|
47
|
+
return filename if filename in ('main', 'lib') else filename
|
|
48
|
+
|
|
49
|
+
last_dir = dirpath[-1]
|
|
50
|
+
|
|
51
|
+
if filename == 'mod' or filename == 'lib':
|
|
52
|
+
return last_dir
|
|
53
|
+
|
|
54
|
+
if filename == last_dir:
|
|
55
|
+
return last_dir
|
|
56
|
+
|
|
57
|
+
return last_dir + '/' + filename
|
|
58
|
+
|
|
59
|
+
def collect_cargo_deps():
|
|
60
|
+
deps = set()
|
|
61
|
+
try:
|
|
62
|
+
with open('Cargo.toml', 'r', errors='replace') as f:
|
|
63
|
+
content = f.read()
|
|
64
|
+
except Exception:
|
|
65
|
+
return deps
|
|
66
|
+
|
|
67
|
+
in_deps = False
|
|
68
|
+
for line in content.split('\n'):
|
|
69
|
+
stripped = line.strip()
|
|
70
|
+
if stripped.startswith('['):
|
|
71
|
+
in_deps = stripped == '[dependencies]'
|
|
72
|
+
continue
|
|
73
|
+
if in_deps and stripped and not stripped.startswith('#'):
|
|
74
|
+
name = stripped.split('=')[0].strip()
|
|
75
|
+
name = re.split(r'[^a-zA-Z0-9_-]', name)[0]
|
|
76
|
+
if name:
|
|
77
|
+
deps.add(name.lower())
|
|
78
|
+
return deps
|
|
79
|
+
|
|
80
|
+
cargo_deps = collect_cargo_deps()
|
|
81
|
+
|
|
82
|
+
files = []
|
|
83
|
+
for root, dirs, fnames in os.walk(SRC_DIR):
|
|
84
|
+
dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'target']
|
|
85
|
+
for fn in fnames:
|
|
86
|
+
if fn.endswith('.rs'):
|
|
87
|
+
files.append(os.path.join(root, fn))
|
|
88
|
+
if len(files) >= 200:
|
|
89
|
+
files = files[:200]
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
if not files:
|
|
93
|
+
sys.exit(0)
|
|
94
|
+
|
|
95
|
+
module_set = set()
|
|
96
|
+
for fpath in files:
|
|
97
|
+
rel = os.path.relpath(fpath, '.').replace(os.sep, '/')
|
|
98
|
+
mod = module_name(rel)
|
|
99
|
+
if mod:
|
|
100
|
+
module_set.add(mod)
|
|
101
|
+
|
|
102
|
+
if len(module_set) > MAX_MODULES:
|
|
103
|
+
module_set = set()
|
|
104
|
+
for fpath in files:
|
|
105
|
+
rel = os.path.relpath(fpath, '.').replace(os.sep, '/')
|
|
106
|
+
parts = rel.split('/')
|
|
107
|
+
if len(parts) > 2:
|
|
108
|
+
module_set.add(parts[1])
|
|
109
|
+
elif len(parts) == 2:
|
|
110
|
+
module_set.add(parts[1].replace('.rs', ''))
|
|
111
|
+
else:
|
|
112
|
+
module_set.add(os.path.splitext(parts[0])[0])
|
|
113
|
+
|
|
114
|
+
edges = set()
|
|
115
|
+
|
|
116
|
+
use_re = re.compile(r'^\s*use\s+(.+?);', re.MULTILINE)
|
|
117
|
+
mod_re = re.compile(r'^\s*mod\s+([a-zA-Z_]\w*)\s*;', re.MULTILINE)
|
|
118
|
+
|
|
119
|
+
for fpath in files:
|
|
120
|
+
rel = os.path.relpath(fpath, '.').replace(os.sep, '/')
|
|
121
|
+
src_mod = module_name(rel)
|
|
122
|
+
if not src_mod or src_mod not in module_set:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
with open(fpath, 'r', errors='replace') as f:
|
|
127
|
+
content = f.read()
|
|
128
|
+
except Exception:
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
for m in mod_re.finditer(content):
|
|
132
|
+
mod_name = m.group(1)
|
|
133
|
+
if mod_name in module_set and mod_name != src_mod:
|
|
134
|
+
edges.add((src_mod, mod_name))
|
|
135
|
+
|
|
136
|
+
for m in use_re.finditer(content):
|
|
137
|
+
path = m.group(1).strip()
|
|
138
|
+
if path.startswith('crate::'):
|
|
139
|
+
crate_path = path[7:]
|
|
140
|
+
first = crate_path.split('::')[0].split('{')[0].strip()
|
|
141
|
+
first = first.replace(',', '').strip()
|
|
142
|
+
if first in module_set and first != src_mod:
|
|
143
|
+
edges.add((src_mod, first))
|
|
144
|
+
elif first and first != src_mod:
|
|
145
|
+
for part in crate_path.split('{'):
|
|
146
|
+
if '}' in part:
|
|
147
|
+
for sub in part.replace('}', '').split(','):
|
|
148
|
+
sub = sub.strip().split('::')[0]
|
|
149
|
+
if sub in module_set and sub != src_mod:
|
|
150
|
+
edges.add((src_mod, sub))
|
|
151
|
+
elif path.startswith('super::'):
|
|
152
|
+
rest = path[7:]
|
|
153
|
+
first = rest.split('::')[0].split('{')[0].strip()
|
|
154
|
+
first = first.replace(',', '').strip()
|
|
155
|
+
fdir = os.path.dirname(rel)
|
|
156
|
+
if SRC_DIR and fdir.startswith(SRC_DIR + '/'):
|
|
157
|
+
fdir = fdir[len(SRC_DIR) + 1:]
|
|
158
|
+
elif fdir == SRC_DIR:
|
|
159
|
+
fdir = ''
|
|
160
|
+
parent_parts = fdir.split('/') if fdir else []
|
|
161
|
+
if parent_parts:
|
|
162
|
+
parent_mod = parent_parts[-1]
|
|
163
|
+
if parent_mod in module_set and parent_mod != src_mod:
|
|
164
|
+
edges.add((src_mod, parent_mod))
|
|
165
|
+
elif path.startswith('self::'):
|
|
166
|
+
pass
|
|
167
|
+
else:
|
|
168
|
+
crate_name = path.split('::')[0].split('{')[0].strip()
|
|
169
|
+
crate_name = crate_name.replace(',', '').strip()
|
|
170
|
+
if crate_name and crate_name not in ('crate', 'super', 'self'):
|
|
171
|
+
if crate_name in cargo_deps:
|
|
172
|
+
edges.add((src_mod, 'ext:' + crate_name))
|
|
173
|
+
|
|
174
|
+
for s, d in sorted(edges):
|
|
175
|
+
sys.stdout.write(s + '\t' + d + '\n')
|
|
176
|
+
" | MAX_ARCH="$MAX_ARCH" bash "$NORMALIZE"
|