@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.
Files changed (96) hide show
  1. package/AI_SETUP_PIPEMD.md +184 -0
  2. package/CHANGELOG.md +47 -0
  3. package/LICENSE +15 -0
  4. package/README.md +535 -0
  5. package/dist/index.js +6647 -0
  6. package/dist/plugins/opencode-server.js +235 -0
  7. package/dist/plugins/opencode-tui.js +914 -0
  8. package/dist/templates/agent-decision-tree.md +113 -0
  9. package/dist/templates/static-rules.md +7 -0
  10. package/package.json +68 -0
  11. package/scripts/C-CPP/architecture/arch.sh +229 -0
  12. package/scripts/C-CPP/lib/limit.sh +146 -0
  13. package/scripts/C-CPP/project/class-diagram.sh +96 -0
  14. package/scripts/C-CPP/project/cmake-targets.sh +68 -0
  15. package/scripts/C-CPP/project/deps.sh +44 -0
  16. package/scripts/C-CPP/project/find-todos.sh +6 -0
  17. package/scripts/C-CPP/project/include-graph.sh +110 -0
  18. package/scripts/C-CPP/project/interfaces.sh +108 -0
  19. package/scripts/C-CPP/project/tree.sh +5 -0
  20. package/scripts/C-CPP/quality/lint.sh +14 -0
  21. package/scripts/C-CPP/quality/test-summary.sh +22 -0
  22. package/scripts/C-CPP/quality/type-check.sh +26 -0
  23. package/scripts/DevOps/architecture/arch.sh +186 -0
  24. package/scripts/DevOps/devops/aws-context.sh +34 -0
  25. package/scripts/DevOps/devops/docker-stats.sh +42 -0
  26. package/scripts/DevOps/devops/k8s-unhealthy.sh +41 -0
  27. package/scripts/DevOps/devops/tf-state.sh +65 -0
  28. package/scripts/DevOps/lib/limit.sh +143 -0
  29. package/scripts/Generic/architecture/arch.sh +570 -0
  30. package/scripts/Generic/lib/limit.sh +140 -0
  31. package/scripts/Go/architecture/arch.sh +79 -0
  32. package/scripts/Go/lib/limit.sh +142 -0
  33. package/scripts/Go/project/deps.sh +35 -0
  34. package/scripts/Go/project/find-todos.sh +6 -0
  35. package/scripts/Go/project/go-interfaces.sh +18 -0
  36. package/scripts/Go/project/go-packages.sh +28 -0
  37. package/scripts/Go/project/tree.sh +5 -0
  38. package/scripts/Go/quality/lint.sh +16 -0
  39. package/scripts/Go/quality/test-summary.sh +16 -0
  40. package/scripts/Go/quality/type-check.sh +16 -0
  41. package/scripts/Node-TypeScript/api/express-routes.sh +14 -0
  42. package/scripts/Node-TypeScript/api/nest-controllers.sh +18 -0
  43. package/scripts/Node-TypeScript/architecture/arch.sh +174 -0
  44. package/scripts/Node-TypeScript/frontend/angular-routes.sh +15 -0
  45. package/scripts/Node-TypeScript/frontend/nextjs-app-router.sh +13 -0
  46. package/scripts/Node-TypeScript/frontend/react-components.sh +20 -0
  47. package/scripts/Node-TypeScript/lib/limit.sh +146 -0
  48. package/scripts/Node-TypeScript/project/deps.sh +15 -0
  49. package/scripts/Node-TypeScript/project/find-todos.sh +6 -0
  50. package/scripts/Node-TypeScript/quality/lint.sh +10 -0
  51. package/scripts/Node-TypeScript/quality/test-summary.sh +39 -0
  52. package/scripts/Node-TypeScript/quality/type-check.sh +10 -0
  53. package/scripts/Python/api/fastapi-routes.sh +12 -0
  54. package/scripts/Python/architecture/arch.sh +220 -0
  55. package/scripts/Python/db/django-models.sh +12 -0
  56. package/scripts/Python/db/sqlalchemy.sh +17 -0
  57. package/scripts/Python/lib/limit.sh +144 -0
  58. package/scripts/Python/project/deps.sh +28 -0
  59. package/scripts/Python/project/find-todos.sh +6 -0
  60. package/scripts/Python/quality/lint.sh +13 -0
  61. package/scripts/Python/quality/test-summary.sh +11 -0
  62. package/scripts/Python/quality/type-check.sh +10 -0
  63. package/scripts/Rust/architecture/arch.sh +176 -0
  64. package/scripts/Rust/lib/limit.sh +142 -0
  65. package/scripts/Rust/project/cargo-deps.sh +42 -0
  66. package/scripts/Rust/project/cargo-features.sh +26 -0
  67. package/scripts/Rust/project/find-todos.sh +6 -0
  68. package/scripts/Rust/project/tree.sh +5 -0
  69. package/scripts/Rust/quality/lint.sh +16 -0
  70. package/scripts/Rust/quality/test-summary.sh +16 -0
  71. package/scripts/Rust/quality/type-check.sh +16 -0
  72. package/scripts/Shared/api/express-routes.sh +11 -0
  73. package/scripts/Shared/api/fastapi-routes.sh +10 -0
  74. package/scripts/Shared/api/nest-controllers.sh +22 -0
  75. package/scripts/Shared/architecture/normalize.sh +178 -0
  76. package/scripts/Shared/crew/crew.sh +15 -0
  77. package/scripts/Shared/db/django-models.sh +11 -0
  78. package/scripts/Shared/db/prisma.sh +33 -0
  79. package/scripts/Shared/db/sqlalchemy.sh +12 -0
  80. package/scripts/Shared/frontend/angular-routes.sh +11 -0
  81. package/scripts/Shared/frontend/nextjs-app-router.sh +13 -0
  82. package/scripts/Shared/frontend/react-components.sh +11 -0
  83. package/scripts/Shared/git/diff-stat.sh +6 -0
  84. package/scripts/Shared/git/git-branch.sh +16 -0
  85. package/scripts/Shared/git/git-log.sh +6 -0
  86. package/scripts/Shared/git/git-status.sh +6 -0
  87. package/scripts/Shared/lib/limit.sh +144 -0
  88. package/scripts/Shared/project/compose-md.sh +182 -0
  89. package/scripts/Shared/project/deps.sh +69 -0
  90. package/scripts/Shared/project/find-todos.sh +6 -0
  91. package/scripts/Shared/project/tree.sh +5 -0
  92. package/scripts/Shared/quality/lint.sh +81 -0
  93. package/scripts/Shared/quality/test-summary.sh +103 -0
  94. package/scripts/Shared/quality/type-check.sh +114 -0
  95. package/scripts/copy-plugins.mjs +4 -0
  96. 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
+ "