@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,113 @@
|
|
|
1
|
+
### Agent Decision Tree
|
|
2
|
+
|
|
3
|
+
You are operating inside a PipeMD context file. The `<!-- pmd: -->` blocks below are live data refreshed by the daemon. This section defines your complete operating workflow — follow it for every task.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
#### 1. Context Gathering — Read Before You Act
|
|
8
|
+
|
|
9
|
+
Before writing a single line of code, gather your bearings from the blocks below. They are your fastest, most accurate source of project truth.
|
|
10
|
+
|
|
11
|
+
| You need… | Read this block | Do NOT run |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Project structure, find a file | `tree` | `tree`, `find`, `ls -R` |
|
|
14
|
+
| Architecture / module graph | `arch` | manual file inspection |
|
|
15
|
+
| Dependencies and versions | `deps` | `cat package.json` |
|
|
16
|
+
| Known TODOs, FIXMEs, HACKs | `todos` | `grep -r TODO` |
|
|
17
|
+
| Recent commits (what changed) | `git-log` | `git log` |
|
|
18
|
+
| Current branch, tracking status | `git-branch` | `git status -b` |
|
|
19
|
+
| Changed / staged / untracked files | `git-status` | `git status` |
|
|
20
|
+
| Diff summary (+/- lines) | `diff-stat` | `git diff --stat` |
|
|
21
|
+
| Type errors | `type-check` | `tsc --noEmit` |
|
|
22
|
+
| Lint errors | `lint` | `eslint .` |
|
|
23
|
+
| Test pass/fail summary | `test-summary` | full test run |
|
|
24
|
+
| Crew: who's working, conflicts | `crew` | `pmd crew status` |
|
|
25
|
+
| DB models, routes, components | ecosystem-specific blocks | manual source scan |
|
|
26
|
+
|
|
27
|
+
If the answer isn't in the blocks — then (and only then) run the appropriate shell command. But check blocks first, always.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
#### 2. Think Before You Code
|
|
32
|
+
|
|
33
|
+
Don't assume. Don't hide confusion. Surface tradeoffs before you type.
|
|
34
|
+
|
|
35
|
+
- **State your plan.** If the task is non-trivial, write a brief plan with verifiable checkpoints:
|
|
36
|
+
```
|
|
37
|
+
1. [Step] → verify: [check]
|
|
38
|
+
2. [Step] → verify: [check]
|
|
39
|
+
3. [Step] → verify: [check]
|
|
40
|
+
```
|
|
41
|
+
- **Surface assumptions.** If multiple interpretations exist, present them — don't pick silently. If something is unclear, stop and ask.
|
|
42
|
+
- **Define success criteria.** Transform vague tasks into verifiable goals:
|
|
43
|
+
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
|
44
|
+
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
|
45
|
+
- "Refactor X" → "Ensure tests pass before and after"
|
|
46
|
+
- **Plan the smallest change.** Identify the exact feature, its source of truth, and its direct dependencies. No speculative features, no "while I'm here" refactors. If a simpler approach exists, say so.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
#### 3. Coordinate — Multi-Agent & Crew Protocol
|
|
51
|
+
|
|
52
|
+
**If a `crew` block is present, you are not alone.** Other agents may be editing the same codebase simultaneously. Follow these rules to avoid conflicts and wasted work.
|
|
53
|
+
|
|
54
|
+
**Before editing any file:**
|
|
55
|
+
|
|
56
|
+
1. Read the `crew` block. It lists every active session, their claimed files, and any conflicts.
|
|
57
|
+
2. If the file is claimed by another agent (`⚠️ CONFLICT`), **stop**. Coordinate with that agent or pick different work. Treat conflicts as blocking.
|
|
58
|
+
3. If edit hooks are installed, claims happen automatically on every file edit. If not, claim manually:
|
|
59
|
+
```bash
|
|
60
|
+
pmd crew claim src/auth.ts --note "refactoring login"
|
|
61
|
+
```
|
|
62
|
+
4. Post your intent so others can see it:
|
|
63
|
+
```bash
|
|
64
|
+
pmd crew note "rewriting the auth middleware"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Sub-agents and parallel workers:**
|
|
68
|
+
|
|
69
|
+
- PipeMD uses a **Harness → Coordinator → Worker** hierarchy. Each harness has one coordinator; workers are sub-agents spawned for parallel tasks.
|
|
70
|
+
- If you are a coordinator spawning sub-agents:
|
|
71
|
+
- Each worker must `pmd crew join --role worker` and export `PMD_SESSION` to get its own session.
|
|
72
|
+
- Partition work by file or directory to minimize overlap. Assign non-overlapping file sets.
|
|
73
|
+
- Workers auto-detect their coordinator via process ancestry — no manual linking needed.
|
|
74
|
+
- Monitor the `crew` block for conflicts between your workers. Resolve immediately.
|
|
75
|
+
- If you are a worker:
|
|
76
|
+
- Your session is derived from your parent process. Claim only the files assigned to you.
|
|
77
|
+
- Check the `crew` block before every edit, not just once at start.
|
|
78
|
+
- Release files when done: `pmd crew release src/auth.ts`.
|
|
79
|
+
|
|
80
|
+
**Staleness and cleanup:**
|
|
81
|
+
|
|
82
|
+
- Sessions without a heartbeat for 90 seconds are considered stale. A fresh heartbeat always outranks a dead-PID guess.
|
|
83
|
+
- Clean up when you're done: `pmd crew leave` removes your session entirely.
|
|
84
|
+
|
|
85
|
+
**Cross-harness coordination:**
|
|
86
|
+
|
|
87
|
+
- PipeMD is harness-neutral. Claude Code, OpenCode, Gemini, Aider, and Cursor agents all share the same crew ledger.
|
|
88
|
+
- The `crew` block renders the union of all sessions — you see everyone, regardless of their harness.
|
|
89
|
+
- If you see a passive agent (listed but without a crew session), they may not have crew hooks installed. Their edits are uncoordinated — exercise extra caution around their files.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
#### 4. Edit — Surgical Discipline
|
|
94
|
+
|
|
95
|
+
- **Match existing code style.** Imports, naming, formatting, conventions — follow what's already there. Don't impose your preferences.
|
|
96
|
+
- **One concern per edit.** If you spot unrelated issues, mention them but don't fix them unless asked.
|
|
97
|
+
- **Never bypass abstractions.** If a file is protected by a service or abstraction layer, use it. Don't modify the underlying file directly.
|
|
98
|
+
- **No logic duplication.** Don't duplicate logic or create parallel states if a source of truth already exists.
|
|
99
|
+
- **No speculative code.** No features beyond what was asked. No abstractions for single-use code. No error handling for impossible scenarios. If you write 200 lines and it could be 50, rewrite it.
|
|
100
|
+
- **Clean up only your own mess.** Remove imports, variables, and functions that your changes made unused. Don't touch pre-existing dead code.
|
|
101
|
+
- **Never edit inside `<!-- pmd: -->` blocks** — your changes are overwritten on the next daemon cycle.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
#### 5. Verify — Close the Loop
|
|
106
|
+
|
|
107
|
+
Every change must be verified. Weak success criteria ("make it work") require constant clarification. Strong criteria let you loop independently.
|
|
108
|
+
|
|
109
|
+
- **Run the project's verification suite.** Check the rules above `<!-- pmd-context -->` or the `deps` block for the correct commands. Typical order: lint → typecheck → test → build.
|
|
110
|
+
- **Re-read the quality blocks.** After your edits, check `type-check` and `lint` blocks for new errors. If active injection is running, validation results appear automatically on your next tool call.
|
|
111
|
+
- **Verify blast radius.** Did your change affect anything beyond the intended scope? Re-read affected blocks to confirm.
|
|
112
|
+
- **Committing?** `git diff --cached` is the only git command you need — staged diffs are not provided by any block. Stage your files, review the diff, then commit.
|
|
113
|
+
- **Release claims.** When done with a file: `pmd crew release src/auth.ts`. When done entirely: `pmd crew leave`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Static Rules & Notes
|
|
2
|
+
|
|
3
|
+
- Content inside `<!-- pmd: -->` blocks is **read-only** — the daemon overwrites it every cycle. Never edit these.
|
|
4
|
+
- Content outside `<!-- pmd: -->` blocks is **yours** — edits persist via bidirectional write-back.
|
|
5
|
+
- PipeMD blocks refresh every few seconds. Trust them — they are cheaper and more current than running shell commands.
|
|
6
|
+
- If blocks are empty or stale, the daemon may be down. Check with `pmd status`.
|
|
7
|
+
- Active injection (`[pmd:…→` messages on tool calls) delivers fresh context automatically — you don't need to re-read the context file between edits.
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipemd-core/pipemd",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The Dynamic Context Harness for AI Coding Agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pmd": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup && node scripts/copy-plugins.mjs && node scripts/copy-templates.mjs",
|
|
11
|
+
"prepare": "pnpm build",
|
|
12
|
+
"prepublishOnly": "pnpm build",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"test:e2e": "bash tests/e2e.sh",
|
|
15
|
+
"test:bidir": "bash tests/e2e-bidirectional.sh",
|
|
16
|
+
"test:scripts": "bash tests/e2e-scripts.sh",
|
|
17
|
+
"test:arch": "bash tests/e2e-architecture.sh",
|
|
18
|
+
"test:compose": "bash tests/e2e-compose.sh",
|
|
19
|
+
"test:crew": "bash tests/e2e-crew.sh",
|
|
20
|
+
"test:inject": "bash tests/e2e-inject.sh",
|
|
21
|
+
"test:unit": "node tests/test-reverse-inject.mjs",
|
|
22
|
+
"test": "pnpm test:unit && pnpm test:e2e && pnpm test:bidir && pnpm test:scripts && pnpm test:arch && pnpm test:compose && pnpm test:crew && pnpm test:inject"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"scripts",
|
|
30
|
+
"AI_SETUP_PIPEMD.md",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"author": "PipeMD Contributors",
|
|
36
|
+
"license": "ISC",
|
|
37
|
+
"homepage": "https://github.com/pipemd-core/pipemd#readme",
|
|
38
|
+
"bugs": { "url": "https://github.com/pipemd-core/pipemd/issues" },
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/pipemd-core/pipemd.git"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"ai",
|
|
45
|
+
"llm",
|
|
46
|
+
"claude",
|
|
47
|
+
"cursor",
|
|
48
|
+
"aider",
|
|
49
|
+
"context",
|
|
50
|
+
"pipe",
|
|
51
|
+
"named-pipe",
|
|
52
|
+
"prompt-context"
|
|
53
|
+
],
|
|
54
|
+
"packageManager": "pnpm@10.33.2",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"chalk": "^5.6.2",
|
|
57
|
+
"chokidar": "^5.0.0",
|
|
58
|
+
"commander": "^14.0.3",
|
|
59
|
+
"prompts": "^2.4.2",
|
|
60
|
+
"yaml": "^2.8.4"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^25.6.0",
|
|
64
|
+
"@types/prompts": "^2.4.9",
|
|
65
|
+
"tsup": "^8.5.1",
|
|
66
|
+
"typescript": "^6.0.3"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Architecture map — C/C++ 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 C/C++ architecture extraction"
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
python3 -c "
|
|
18
|
+
import os, re, sys
|
|
19
|
+
from collections import defaultdict
|
|
20
|
+
|
|
21
|
+
MAX_MODULES = 40
|
|
22
|
+
SKIP_DIRS = {'build', 'cmake-build-debug', 'cmake-build-release', '_deps',
|
|
23
|
+
'third_party', 'external', '.git', '.pipemd', 'CMakeFiles'}
|
|
24
|
+
|
|
25
|
+
def module_name(rel_path):
|
|
26
|
+
rel_path = rel_path.replace(os.sep, '/')
|
|
27
|
+
parts = rel_path.split('/')
|
|
28
|
+
filename = os.path.splitext(parts[-1])[0] if parts else ''
|
|
29
|
+
dirpath = parts[:-1] if len(parts) > 1 else []
|
|
30
|
+
|
|
31
|
+
if not dirpath:
|
|
32
|
+
return filename
|
|
33
|
+
|
|
34
|
+
last_dir = dirpath[-1]
|
|
35
|
+
|
|
36
|
+
if filename == last_dir:
|
|
37
|
+
return last_dir
|
|
38
|
+
|
|
39
|
+
if len(dirpath) >= 2:
|
|
40
|
+
parent = dirpath[-2]
|
|
41
|
+
if parent in ('src', 'lib', 'include', 'app'):
|
|
42
|
+
return last_dir + '/' + filename
|
|
43
|
+
|
|
44
|
+
return last_dir + '/' + filename
|
|
45
|
+
|
|
46
|
+
def collect_cmake_deps():
|
|
47
|
+
deps = {}
|
|
48
|
+
target_sources = {}
|
|
49
|
+
for root, dirs, fnames in os.walk('.'):
|
|
50
|
+
dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
|
|
51
|
+
if 'CMakeLists.txt' in fnames:
|
|
52
|
+
cmake_path = os.path.join(root, 'CMakeLists.txt')
|
|
53
|
+
try:
|
|
54
|
+
with open(cmake_path, 'r', errors='replace') as f:
|
|
55
|
+
content = f.read()
|
|
56
|
+
except Exception:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
content = re.sub(r'#.*', '', content)
|
|
60
|
+
content = content.replace('\\\n', ' ')
|
|
61
|
+
content = re.sub(r'\s+', ' ', content)
|
|
62
|
+
|
|
63
|
+
for m in re.finditer(r'add_(?:library|executable)\s*\(\s*(\w+)\s+(.*?)(?:\))', content, re.DOTALL):
|
|
64
|
+
target = m.group(1)
|
|
65
|
+
sources_str = m.group(2)
|
|
66
|
+
srcs = []
|
|
67
|
+
for src_m in re.finditer(r'(\S+\.(?:cpp|cc|cxx|c|h|hpp|hh))', sources_str):
|
|
68
|
+
srcs.append(src_m.group(1))
|
|
69
|
+
if srcs:
|
|
70
|
+
target_sources[target.lower()] = srcs
|
|
71
|
+
|
|
72
|
+
for m in re.finditer(r'target_link_libraries\s*\(\s*(\w+)\s+(.*?)\)', content, re.DOTALL):
|
|
73
|
+
target = m.group(1)
|
|
74
|
+
libs_str = m.group(2)
|
|
75
|
+
libs = []
|
|
76
|
+
for lib_m in re.finditer(r'(\w[\w:]*)', libs_str):
|
|
77
|
+
lib = lib_m.group(1)
|
|
78
|
+
if lib in ('PUBLIC', 'PRIVATE', 'INTERFACE', 'REQUIRED'):
|
|
79
|
+
continue
|
|
80
|
+
lib_name = lib.split('::')[-1].lower()
|
|
81
|
+
libs.append(lib_name)
|
|
82
|
+
if libs:
|
|
83
|
+
deps[target.lower()] = libs
|
|
84
|
+
|
|
85
|
+
return deps, target_sources
|
|
86
|
+
|
|
87
|
+
cmake_deps, cmake_targets = collect_cmake_deps()
|
|
88
|
+
|
|
89
|
+
include_dirs = ['include', 'inc', 'src', 'lib', '.']
|
|
90
|
+
include_path_cache = {}
|
|
91
|
+
|
|
92
|
+
def find_include_file(inc_path, source_dir):
|
|
93
|
+
key = (inc_path, source_dir)
|
|
94
|
+
if key in include_path_cache:
|
|
95
|
+
return include_path_cache[key]
|
|
96
|
+
|
|
97
|
+
for inc_dir in [source_dir] + include_dirs:
|
|
98
|
+
full = os.path.join(inc_dir, inc_path)
|
|
99
|
+
if os.path.isfile(full):
|
|
100
|
+
include_path_cache[key] = full
|
|
101
|
+
return full
|
|
102
|
+
|
|
103
|
+
for root, dirs, fnames in os.walk('.'):
|
|
104
|
+
dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith('.')]
|
|
105
|
+
basename = os.path.basename(inc_path)
|
|
106
|
+
for fn in fnames:
|
|
107
|
+
if fn == basename:
|
|
108
|
+
full = os.path.join(root, fn)
|
|
109
|
+
include_path_cache[key] = full
|
|
110
|
+
return full
|
|
111
|
+
|
|
112
|
+
include_path_cache[key] = None
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
files = []
|
|
116
|
+
for root, dirs, fnames in os.walk('.'):
|
|
117
|
+
dirs[:] = [d for d in sorted(dirs) if d not in SKIP_DIRS and not d.startswith('.')]
|
|
118
|
+
for fn in sorted(fnames):
|
|
119
|
+
if fn.endswith(('.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.hxx', '.hh')):
|
|
120
|
+
if '.test.' not in fn and '.spec.' not in fn:
|
|
121
|
+
files.append(os.path.join(root, fn))
|
|
122
|
+
if len(files) >= 300:
|
|
123
|
+
files = files[:300]
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
if not files:
|
|
127
|
+
sys.exit(0)
|
|
128
|
+
|
|
129
|
+
module_set = set()
|
|
130
|
+
for fpath in files:
|
|
131
|
+
rel = os.path.relpath(fpath, '.').replace(os.sep, '/')
|
|
132
|
+
skip = False
|
|
133
|
+
for sd in SKIP_DIRS:
|
|
134
|
+
if '/' + sd + '/' in '/' + rel or rel.startswith(sd + '/'):
|
|
135
|
+
skip = True
|
|
136
|
+
break
|
|
137
|
+
if skip:
|
|
138
|
+
continue
|
|
139
|
+
mod = module_name(rel)
|
|
140
|
+
if mod:
|
|
141
|
+
module_set.add(mod)
|
|
142
|
+
|
|
143
|
+
if len(module_set) > MAX_MODULES:
|
|
144
|
+
module_set = set()
|
|
145
|
+
for fpath in files:
|
|
146
|
+
rel = os.path.relpath(fpath, '.').replace(os.sep, '/')
|
|
147
|
+
parts = rel.split('/')
|
|
148
|
+
if len(parts) > 2:
|
|
149
|
+
module_set.add(parts[0] + '/' + parts[1])
|
|
150
|
+
elif len(parts) == 2:
|
|
151
|
+
module_set.add(parts[0] if parts[0] in ('src', 'lib', 'include', 'app') else parts[1].rsplit('.', 1)[0])
|
|
152
|
+
else:
|
|
153
|
+
module_set.add(os.path.splitext(parts[0])[0])
|
|
154
|
+
|
|
155
|
+
include_re = re.compile(r'^\\s*#\\s*include\\s*\"([^\"]+)\"', re.MULTILINE)
|
|
156
|
+
|
|
157
|
+
edges = set()
|
|
158
|
+
|
|
159
|
+
for fpath in files:
|
|
160
|
+
rel = os.path.relpath(fpath, '.').replace(os.sep, '/')
|
|
161
|
+
skip = False
|
|
162
|
+
for sd in SKIP_DIRS:
|
|
163
|
+
if '/' + sd + '/' in '/' + rel or rel.startswith(sd + '/'):
|
|
164
|
+
skip = True
|
|
165
|
+
break
|
|
166
|
+
if skip:
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
src_mod = module_name(rel)
|
|
170
|
+
if not src_mod or src_mod not in module_set:
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
with open(fpath, 'r', errors='replace') as f:
|
|
175
|
+
content = f.read()
|
|
176
|
+
except Exception:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
source_dir = os.path.dirname(fpath)
|
|
180
|
+
|
|
181
|
+
for m in include_re.finditer(content):
|
|
182
|
+
inc_path = m.group(1)
|
|
183
|
+
found = find_include_file(inc_path, source_dir)
|
|
184
|
+
if found:
|
|
185
|
+
target_rel = os.path.relpath(found, '.').replace(os.sep, '/')
|
|
186
|
+
target_mod = module_name(target_rel)
|
|
187
|
+
if target_mod and target_mod in module_set and target_mod != src_mod:
|
|
188
|
+
edges.add((src_mod, target_mod))
|
|
189
|
+
else:
|
|
190
|
+
inc_dir = os.path.dirname(inc_path)
|
|
191
|
+
if inc_dir and inc_dir != '.':
|
|
192
|
+
target_mod = inc_dir.split('/')[-1]
|
|
193
|
+
if target_mod in module_set and target_mod != src_mod:
|
|
194
|
+
edges.add((src_mod, target_mod))
|
|
195
|
+
else:
|
|
196
|
+
base = os.path.splitext(os.path.basename(inc_path))[0]
|
|
197
|
+
if base in module_set and base != src_mod:
|
|
198
|
+
edges.add((src_mod, base))
|
|
199
|
+
|
|
200
|
+
for target, libs in cmake_deps.items():
|
|
201
|
+
target_mods = []
|
|
202
|
+
if target in cmake_targets:
|
|
203
|
+
for src in cmake_targets[target]:
|
|
204
|
+
src_rel = src.replace(os.sep, '/')
|
|
205
|
+
mod = module_name(src_rel)
|
|
206
|
+
if mod and mod in module_set:
|
|
207
|
+
target_mods.append(mod)
|
|
208
|
+
if not target_mods:
|
|
209
|
+
if target in module_set:
|
|
210
|
+
target_mods = [target]
|
|
211
|
+
else:
|
|
212
|
+
for m in module_set:
|
|
213
|
+
if m.endswith('/' + target) or m == target:
|
|
214
|
+
target_mods.append(m)
|
|
215
|
+
break
|
|
216
|
+
for tmod in target_mods:
|
|
217
|
+
for lib in libs:
|
|
218
|
+
if lib in module_set:
|
|
219
|
+
edges.add((tmod, lib))
|
|
220
|
+
elif any(m.endswith('/' + lib) or m == lib for m in module_set):
|
|
221
|
+
for m in module_set:
|
|
222
|
+
if m.endswith('/' + lib) or m == lib:
|
|
223
|
+
edges.add((tmod, m))
|
|
224
|
+
else:
|
|
225
|
+
edges.add((tmod, 'ext:' + lib))
|
|
226
|
+
|
|
227
|
+
for s, d in sorted(edges):
|
|
228
|
+
sys.stdout.write(s + '\\t' + d + '\\n')
|
|
229
|
+
" | MAX_ARCH="$MAX_ARCH" bash "$NORMALIZE"
|
|
@@ -0,0 +1,146 @@
|
|
|
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:-node_modules|.git|.pipemd|dist|coverage|build|cmake-build-*|_deps|.cache|compile_commands.json}"
|
|
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 '*/build/*' \
|
|
135
|
+
-not -path '*/cmake-build-*/*' \
|
|
136
|
+
-not -path '*/_deps/*' \
|
|
137
|
+
-not -path '*/.git/*' \
|
|
138
|
+
-not -path '*/.pipemd/*' \
|
|
139
|
+
-not -path '*/.cache/*' \
|
|
140
|
+
-not -name 'build' \
|
|
141
|
+
-not -name '.git' \
|
|
142
|
+
-not -name '.pipemd' \
|
|
143
|
+
-not -name 'compile_commands.json' \
|
|
144
|
+
2>/dev/null | head -"$max" | sort
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Class diagram — Mermaid classDiagram from C++ headers
|
|
4
|
+
source "$(dirname "$0")/../lib/limit.sh"
|
|
5
|
+
|
|
6
|
+
if ! command -v python3 &>/dev/null; then
|
|
7
|
+
echo "python3 is required for C++ class diagram extraction"
|
|
8
|
+
exit 0
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
HEADERS=$(find . -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.hxx" \) \
|
|
12
|
+
-not -path "*/build/*" -not -path "*/.git/*" -not -path "*/cmake-build-*/*" \
|
|
13
|
+
-not -path "*/_deps/*" -not -path "*/third_party/*" -not -path "*/external/*" \
|
|
14
|
+
2>/dev/null | head -30)
|
|
15
|
+
|
|
16
|
+
if [ -z "$HEADERS" ]; then
|
|
17
|
+
echo "No C++ headers found"
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "$HEADERS" | PMDMAX="${MAX_CLASS:-20}" python3 -c "
|
|
22
|
+
import sys, os, re
|
|
23
|
+
|
|
24
|
+
MAX = int(os.environ.get('PMDMAX', '20'))
|
|
25
|
+
|
|
26
|
+
headers = []
|
|
27
|
+
for line in sys.stdin:
|
|
28
|
+
line = line.strip()
|
|
29
|
+
if line and os.path.isfile(line):
|
|
30
|
+
headers.append(line)
|
|
31
|
+
|
|
32
|
+
if not headers:
|
|
33
|
+
print('No C++ headers found')
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
|
|
36
|
+
classes = {}
|
|
37
|
+
|
|
38
|
+
class_re = re.compile(r'^\s*(?:class|struct)\s+([A-Za-z_]\w*)\s*(?::\s*(.*?))?\s*\{', re.MULTILINE)
|
|
39
|
+
virt_re = re.compile(r'^\s*virtual\s+([~]?[\w:&<>, ]+?)\s+\w+\s*\([^)]*\)\s*(?:const)?\s*=\s*0\s*;')
|
|
40
|
+
base_re = re.compile(r'(?:public|protected|private)\s+([A-Za-z_]\w*)')
|
|
41
|
+
|
|
42
|
+
for hdr in headers:
|
|
43
|
+
try:
|
|
44
|
+
with open(hdr, 'r', errors='replace') as f:
|
|
45
|
+
content = f.read()
|
|
46
|
+
except Exception:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
content = re.sub(r'//.*', '', content)
|
|
50
|
+
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
|
|
51
|
+
|
|
52
|
+
for m in class_re.finditer(content):
|
|
53
|
+
name = m.group(1)
|
|
54
|
+
if name in classes:
|
|
55
|
+
continue
|
|
56
|
+
bases_str = m.group(2) or ''
|
|
57
|
+
bases = [b.strip() for b in base_re.findall(bases_str)]
|
|
58
|
+
|
|
59
|
+
start = m.end()
|
|
60
|
+
depth = 1
|
|
61
|
+
end = start
|
|
62
|
+
while end < len(content) and depth > 0:
|
|
63
|
+
if content[end] == '{':
|
|
64
|
+
depth += 1
|
|
65
|
+
elif content[end] == '}':
|
|
66
|
+
depth -= 1
|
|
67
|
+
end += 1
|
|
68
|
+
|
|
69
|
+
body = content[start:end]
|
|
70
|
+
|
|
71
|
+
methods = []
|
|
72
|
+
for vm in virt_re.finditer(body):
|
|
73
|
+
methods.append(vm.group(1).strip() + '()')
|
|
74
|
+
|
|
75
|
+
classes[name] = {'bases': bases, 'methods': methods[:5], 'file': os.path.basename(hdr)}
|
|
76
|
+
|
|
77
|
+
if not classes:
|
|
78
|
+
print('No class/struct definitions found in headers')
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
lines = ['classDiagram']
|
|
82
|
+
count = 0
|
|
83
|
+
for name in sorted(classes.keys(), key=lambda n: (len(classes[n]['bases']), n)):
|
|
84
|
+
info = classes[name]
|
|
85
|
+
if count >= MAX:
|
|
86
|
+
break
|
|
87
|
+
count += 1
|
|
88
|
+
lines.append(f' class {name} {{')
|
|
89
|
+
for m in info['methods']:
|
|
90
|
+
lines.append(f' {m}')
|
|
91
|
+
lines.append(' }')
|
|
92
|
+
for base in info['bases']:
|
|
93
|
+
lines.append(f' {base} <|-- {name}')
|
|
94
|
+
|
|
95
|
+
print('\n'.join(lines))
|
|
96
|
+
"
|