fix(security): 添加VITE_PAYMENT_URL环境变量配置
This commit is contained in:
851
.qoder/skills/understand/SKILL.md
Normal file
851
.qoder/skills/understand/SKILL.md
Normal file
@@ -0,0 +1,851 @@
|
||||
---
|
||||
name: understand
|
||||
description: Analyze a codebase to produce an interactive knowledge graph for understanding architecture, components, and relationships
|
||||
argument-hint: ["[path] [--full|--auto-update|--no-auto-update|--review|--language <lang>]"]
|
||||
---
|
||||
|
||||
# /understand
|
||||
|
||||
Analyze the current codebase and produce a `knowledge-graph.json` file in `.understand-anything/`. This file powers the interactive dashboard for exploring the project's architecture.
|
||||
|
||||
## Options
|
||||
|
||||
- `$ARGUMENTS` may contain:
|
||||
- `--full` — Force a full rebuild, ignoring any existing graph
|
||||
- `--auto-update` — Enable automatic graph updates on commit (writes `autoUpdate: true` to `.understand-anything/config.json`)
|
||||
- `--no-auto-update` — Disable automatic graph updates (writes `autoUpdate: false` to `.understand-anything/config.json`)
|
||||
- `--review` — Run full LLM graph-reviewer instead of inline deterministic validation
|
||||
- `--language <lang>` — Generate all textual content (summaries, descriptions, tags, titles, languageNotes, languageLesson) in the specified language. Accepts ISO 639-1 codes (`zh`, `ja`, `ko`, `en`, `es`, `fr`, `de`, etc.) or friendly names (`chinese`, `japanese`, `korean`, `english`, `spanish`, etc.). Locale variants supported: `zh-TW`, `zh-HK`, etc. Defaults to `en` (English). Stores preference in `.understand-anything/config.json` for consistency across incremental updates.
|
||||
- A directory path (e.g. `/path/to/repo` or `../other-project`) — Analyze the given directory instead of the current working directory
|
||||
|
||||
---
|
||||
|
||||
## Progress Reporting
|
||||
|
||||
Throughout execution, report progress to the user at each phase transition and during batch processing. This keeps users informed on large codebases where analysis can take a long time.
|
||||
|
||||
- **Phase transitions:** At the start of each phase, print a status line:
|
||||
> `[Phase N/7] <phase name>...`
|
||||
>
|
||||
> Example: `[Phase 2/7] Analyzing files (12 batches)...`
|
||||
|
||||
- **Batch progress:** During Phase 2, report each batch with its index and total:
|
||||
> `Analyzing batch X/N (files: foo.ts, bar.ts, ...)` (list up to 3 filenames, then `...` if more)
|
||||
|
||||
- **Phase completion:** When a phase finishes, briefly confirm:
|
||||
> `Phase N complete. <one-line summary of result>`
|
||||
>
|
||||
> Example: `Phase 1 complete. Found 247 files across 3 languages.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Pre-flight
|
||||
|
||||
Determine whether to run a full analysis or incremental update.
|
||||
|
||||
1. **Resolve `PROJECT_ROOT`:**
|
||||
- Parse `$ARGUMENTS` for a non-flag token (any argument that does not start with `--`). If found, treat it as the target directory path.
|
||||
- If the path is relative, resolve it against the current working directory.
|
||||
- Verify the resolved path exists and is a directory (run `test -d <path>`). If it does not exist or is not a directory, report an error to the user and **STOP**.
|
||||
- Set `PROJECT_ROOT` to the resolved absolute path.
|
||||
- If no directory path argument is found, set `PROJECT_ROOT` to the current working directory.
|
||||
- **Worktree redirect.** If `PROJECT_ROOT` is inside a git worktree (not the main checkout), redirect output to the main repository root. Worktrees managed by Claude Code are ephemeral — `.understand-anything/` written there is destroyed when the session ends, taking the knowledge graph with it (issue #133). Detect a worktree by comparing `git rev-parse --git-dir` against `git rev-parse --git-common-dir`; in a normal checkout or submodule they resolve to the same path, in a worktree they differ and the parent of `--git-common-dir` is the main repo root.
|
||||
|
||||
```bash
|
||||
COMMON_DIR=$(git -C "$PROJECT_ROOT" rev-parse --git-common-dir 2>/dev/null)
|
||||
GIT_DIR=$(git -C "$PROJECT_ROOT" rev-parse --git-dir 2>/dev/null)
|
||||
if [ -n "$COMMON_DIR" ] && [ -n "$GIT_DIR" ]; then
|
||||
COMMON_ABS=$(cd "$PROJECT_ROOT" && cd "$COMMON_DIR" 2>/dev/null && pwd -P)
|
||||
GIT_ABS=$(cd "$PROJECT_ROOT" && cd "$GIT_DIR" 2>/dev/null && pwd -P)
|
||||
if [ -n "$COMMON_ABS" ] && [ "$COMMON_ABS" != "$GIT_ABS" ]; then
|
||||
MAIN_ROOT=$(dirname "$COMMON_ABS")
|
||||
if [ -d "$MAIN_ROOT" ] && [ "${UNDERSTAND_NO_WORKTREE_REDIRECT:-0}" != "1" ]; then
|
||||
echo "[understand] Detected git worktree at $PROJECT_ROOT"
|
||||
echo "[understand] Redirecting output to main repo root: $MAIN_ROOT"
|
||||
echo "[understand] (Set UNDERSTAND_NO_WORKTREE_REDIRECT=1 to keep PROJECT_ROOT as the worktree.)"
|
||||
PROJECT_ROOT="$MAIN_ROOT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
Set `UNDERSTAND_NO_WORKTREE_REDIRECT=1` if you intentionally want a per-worktree graph (rare — most users want the redirect).
|
||||
1.5. **Ensure the plugin is built.** Later phases invoke Node scripts that import `@understand-anything/core`. On a fresh install `packages/core/dist/` does not exist yet — build once.
|
||||
|
||||
**Important:** do **not** assume the plugin root is simply two directories above the skill path string. In many installations `~/.agents/skills/understand` is a symlink into the real plugin checkout. Prefer runtime-provided plugin roots first (for Claude), then fall back to universal symlinks, skill symlink resolution, and common clone-based install paths.
|
||||
|
||||
Resolve the plugin root like this:
|
||||
|
||||
```bash
|
||||
SKILL_REAL=$(realpath ~/.agents/skills/understand 2>/dev/null || readlink -f ~/.agents/skills/understand 2>/dev/null || echo "")
|
||||
SELF_RELATIVE=$([ -n "$SKILL_REAL" ] && cd "$SKILL_REAL/../.." 2>/dev/null && pwd || echo "")
|
||||
COPILOT_SKILL_REAL=$(realpath ~/.copilot/skills/understand 2>/dev/null || readlink -f ~/.copilot/skills/understand 2>/dev/null || echo "")
|
||||
COPILOT_SELF_RELATIVE=$([ -n "$COPILOT_SKILL_REAL" ] && cd "$COPILOT_SKILL_REAL/../.." 2>/dev/null && pwd || echo "")
|
||||
|
||||
PLUGIN_ROOT=""
|
||||
for candidate in \
|
||||
"${CLAUDE_PLUGIN_ROOT}" \
|
||||
"$HOME/.understand-anything-plugin" \
|
||||
"$SELF_RELATIVE" \
|
||||
"$COPILOT_SELF_RELATIVE" \
|
||||
"$HOME/.codex/understand-anything/understand-anything-plugin" \
|
||||
"$HOME/.opencode/understand-anything/understand-anything-plugin" \
|
||||
"$HOME/.pi/understand-anything/understand-anything-plugin" \
|
||||
"$HOME/understand-anything/understand-anything-plugin"; do
|
||||
if [ -n "$candidate" ] && [ -f "$candidate/package.json" ] && [ -f "$candidate/pnpm-workspace.yaml" ]; then
|
||||
PLUGIN_ROOT="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$PLUGIN_ROOT" ]; then
|
||||
echo "Error: Cannot find the understand-anything plugin root."
|
||||
echo "Checked:"
|
||||
echo " - ${CLAUDE_PLUGIN_ROOT:-<unset CLAUDE_PLUGIN_ROOT>}"
|
||||
echo " - $HOME/.understand-anything-plugin"
|
||||
echo " - ${SELF_RELATIVE:-<unresolved path derived from ~/.agents/skills/understand>}"
|
||||
echo " - ${COPILOT_SELF_RELATIVE:-<unresolved path derived from ~/.copilot/skills/understand>}"
|
||||
echo " - $HOME/.codex/understand-anything/understand-anything-plugin"
|
||||
echo " - $HOME/.opencode/understand-anything/understand-anything-plugin"
|
||||
echo " - $HOME/.pi/understand-anything/understand-anything-plugin"
|
||||
echo " - $HOME/understand-anything/understand-anything-plugin"
|
||||
echo "Make sure the plugin is installed correctly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$PLUGIN_ROOT/packages/core/dist/index.js" ]; then
|
||||
cd "$PLUGIN_ROOT" && (pnpm install --frozen-lockfile 2>/dev/null || pnpm install) && pnpm --filter @understand-anything/core build
|
||||
fi
|
||||
```
|
||||
|
||||
If `pnpm` is missing, report to the user: "Install Node.js ≥ 22 and pnpm ≥ 10, then re-run `/understand`."
|
||||
|
||||
2. Get the current git commit hash:
|
||||
```bash
|
||||
git rev-parse HEAD
|
||||
```
|
||||
3. Create the intermediate and temp output directories:
|
||||
```bash
|
||||
mkdir -p $PROJECT_ROOT/.understand-anything/intermediate
|
||||
mkdir -p $PROJECT_ROOT/.understand-anything/tmp
|
||||
```
|
||||
3.5. **Auto-update configuration:**
|
||||
- If `--auto-update` is in `$ARGUMENTS`: write `{"autoUpdate": true}` to `$PROJECT_ROOT/.understand-anything/config.json`
|
||||
- If `--no-auto-update` is in `$ARGUMENTS`: write `{"autoUpdate": false}` to `$PROJECT_ROOT/.understand-anything/config.json`
|
||||
- These flags only set the config — analysis proceeds normally regardless.
|
||||
|
||||
3.6. **Language configuration:**
|
||||
- Parse `$ARGUMENTS` for `--language <lang>` flag. If found, extract the language code.
|
||||
- **Language code normalization:** Map friendly names to ISO codes:
|
||||
- `chinese` → `zh`, `japanese` → `ja`, `korean` → `ko`, `english` → `en`, `spanish` → `es`, `french` → `fr`, `german` → `de`, `portuguese` → `pt`, `russian` → `ru`, `arabic` → `ar`, etc.
|
||||
- Locale variants: `zh-TW`, `zh-HK`, `zh-CN`, `pt-BR`, etc. are preserved as-is.
|
||||
- If `--language` is NOT specified:
|
||||
- Check `$PROJECT_ROOT/.understand-anything/config.json` for an existing `outputLanguage` field. If present, use that.
|
||||
- If no stored preference, default to `en` (English).
|
||||
- If `--language` IS specified:
|
||||
- Update `$PROJECT_ROOT/.understand-anything/config.json` with the new language: merge `{"outputLanguage": "<lang>"}` into existing config.
|
||||
- Store as `$OUTPUT_LANGUAGE` for use throughout all phases.
|
||||
- **Language directive template:** Store as `$LANGUAGE_DIRECTIVE`:
|
||||
```markdown
|
||||
> **Language directive**: Generate all textual content (summaries, descriptions, tags, titles, languageNotes, languageLesson) in **{language}**. Maintain technical accuracy while using natural, native-level phrasing in the target language. Keep technical terms in English when no standard translation exists (e.g., "middleware", "hook", "barrel").
|
||||
```
|
||||
|
||||
4. **Check for subdomain knowledge graphs to merge:**
|
||||
List all `*knowledge-graph*.json` files in `$PROJECT_ROOT/.understand-anything/` **excluding** `knowledge-graph.json` itself (e.g. `frontend-knowledge-graph.json`, `backend-knowledge-graph.json`). If any subdomain graphs exist, run the merge script bundled with this skill (located next to this SKILL.md file — use the skill directory path, not the project root):
|
||||
```bash
|
||||
python <SKILL_DIR>/merge-subdomain-graphs.py $PROJECT_ROOT
|
||||
```
|
||||
The script discovers subdomain graphs, loads the existing `knowledge-graph.json` as a base (if present), and merges everything into `knowledge-graph.json` (deduplicating nodes and edges). Report the merge summary to the user, then continue with the merged graph.
|
||||
|
||||
5. Check if `$PROJECT_ROOT/.understand-anything/knowledge-graph.json` exists. If it does, read it.
|
||||
6. Check if `$PROJECT_ROOT/.understand-anything/meta.json` exists. If it does, read it to get `gitCommitHash`.
|
||||
7. **Decision logic:**
|
||||
|
||||
| Condition | Action |
|
||||
|---|---|
|
||||
| `--full` flag in `$ARGUMENTS` | Full analysis (all phases) |
|
||||
| No existing graph or meta | Full analysis (all phases) |
|
||||
| `--review` flag + existing graph + unchanged commit hash | Skip to Phase 6 (review-only — reuse existing assembled graph) |
|
||||
| Existing graph + unchanged commit hash | Ask the user: "The graph is up to date at this commit. Would you like to: **(a)** run a full rebuild (`--full`), **(b)** run the LLM graph reviewer (`--review`), or **(c)** do nothing?" Then follow their choice. If they pick (c), STOP. |
|
||||
| Existing graph + changed files | Incremental update (re-analyze changed files only) |
|
||||
|
||||
**Review-only path:** Copy the existing `knowledge-graph.json` to `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`, then jump directly to Phase 6 step 3.
|
||||
|
||||
For incremental updates, get the changed file list:
|
||||
```bash
|
||||
git diff <lastCommitHash>..HEAD --name-only
|
||||
```
|
||||
If this returns no files, report "Graph is up to date" and STOP.
|
||||
|
||||
8. **Collect project context for subagent injection:**
|
||||
- Read `README.md` (or `README.rst`, `readme.md`) from `$PROJECT_ROOT` if it exists. Store as `$README_CONTENT` (first 3000 characters).
|
||||
- Read the primary package manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`) if it exists. Store as `$MANIFEST_CONTENT`.
|
||||
- Capture the top-level directory tree:
|
||||
```bash
|
||||
find $PROJECT_ROOT -maxdepth 2 -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' | head -100
|
||||
```
|
||||
Store as `$DIR_TREE`.
|
||||
- Detect the project entry point by checking for common patterns (in order): `src/index.ts`, `src/main.ts`, `src/App.tsx`, `index.js`, `main.py`, `manage.py`, `app.py`, `wsgi.py`, `asgi.py`, `run.py`, `__main__.py`, `main.go`, `cmd/*/main.go`, `src/main.rs`, `src/lib.rs`, `src/main/java/**/Application.java`, `Program.cs`, `config.ru`, `index.php`. Store first match as `$ENTRY_POINT`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0.5 — Ignore Configuration
|
||||
|
||||
Set up and verify the `.understandignore` file before scanning.
|
||||
|
||||
1. Check if `$PROJECT_ROOT/.understand-anything/.understandignore` exists.
|
||||
2. **If it does NOT exist**, generate a starter file:
|
||||
- Run the following Node.js one-liner in `$PROJECT_ROOT` (reads `.gitignore` and deduplicates against built-in defaults):
|
||||
```bash
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const root = process.cwd();
|
||||
const defaults = ['node_modules/','node_modules','.git/','vendor/','venv/','.venv/','__pycache__/','dist/','dist','build/','build','out/','coverage/','coverage','.next/','.cache/','.turbo/','target/','obj/','*.lock','package-lock.json','yarn.lock','pnpm-lock.yaml','*.png','*.jpg','*.jpeg','*.gif','*.svg','*.ico','*.woff','*.woff2','*.ttf','*.eot','*.mp3','*.mp4','*.pdf','*.zip','*.tar','*.gz','*.min.js','*.min.css','*.map','*.generated.*','.idea/','.vscode/','LICENSE','.gitignore','.editorconfig','.prettierrc','.eslintrc*','*.log'];
|
||||
const norm = p => p.replace(/\/+$/, '');
|
||||
const defaultSet = new Set(defaults.map(norm));
|
||||
const header = '# .understandignore — patterns for files/dirs to exclude from analysis\n# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs)\n# Lines below are suggestions — uncomment to activate.\n# Use ! prefix to force-include something excluded by defaults.\n#\n# Built-in defaults (always excluded unless negated):\n# node_modules/, .git/, dist/, build/, obj/, *.lock, *.min.js, etc.\n#\n';
|
||||
let body = '';
|
||||
const gitignorePath = path.join(root, '.gitignore');
|
||||
if (fs.existsSync(gitignorePath)) {
|
||||
const gi = fs.readFileSync(gitignorePath, 'utf-8').split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')).filter(p => !defaultSet.has(norm(p)));
|
||||
if (gi.length) { body += '# --- From .gitignore (uncomment to exclude) ---\n\n' + gi.map(p => '# ' + p).join('\n') + '\n\n'; }
|
||||
}
|
||||
const dirs = ['__tests__','test','tests','fixtures','testdata','docs','examples','scripts','migrations','.storybook'];
|
||||
const found = dirs.filter(d => fs.existsSync(path.join(root, d)));
|
||||
if (found.length) { body += '# --- Detected directories (uncomment to exclude) ---\n\n' + found.map(d => '# ' + d + '/').join('\n') + '\n\n'; }
|
||||
body += '# --- Test file patterns (uncomment to exclude) ---\n\n# *.test.*\n# *.spec.*\n# *.snap\n';
|
||||
const outDir = path.join(root, '.understand-anything');
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(outDir, '.understandignore'), header + body);
|
||||
"
|
||||
```
|
||||
- Report to the user:
|
||||
> Generated `.understand-anything/.understandignore` with suggested exclusions based on your project structure. Please review it and uncomment any patterns you'd like to exclude from analysis. When ready, confirm to continue.
|
||||
- **Wait for user confirmation before proceeding.**
|
||||
3. **If it already exists**, report:
|
||||
> Found `.understand-anything/.understandignore`. Review it if needed, then confirm to continue.
|
||||
- **Wait for user confirmation before proceeding.**
|
||||
4. After confirmation, proceed to Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — SCAN (Full analysis only)
|
||||
|
||||
Report to the user: `[Phase 1/7] Scanning project files...`
|
||||
|
||||
Dispatch a subagent using the `project-scanner` agent definition (at `agents/project-scanner.md`). Append the following additional context:
|
||||
|
||||
> **Additional context from main session:**
|
||||
>
|
||||
> Project README (first 3000 chars):
|
||||
> ```
|
||||
> $README_CONTENT
|
||||
> ```
|
||||
>
|
||||
> Package manifest:
|
||||
> ```
|
||||
> $MANIFEST_CONTENT
|
||||
> ```
|
||||
>
|
||||
> Use this context to produce more accurate project name, description, and framework detection. The README and manifest are authoritative — prefer their information over heuristics.
|
||||
>
|
||||
> $LANGUAGE_DIRECTIVE
|
||||
|
||||
Pass these parameters in the dispatch prompt:
|
||||
|
||||
> Scan this project directory to discover all project files (including non-code files like configs, docs, infrastructure), detect languages and frameworks.
|
||||
> Project root: `$PROJECT_ROOT`
|
||||
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/scan-result.json`
|
||||
|
||||
After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/scan-result.json` to get:
|
||||
- Project name, description
|
||||
- Languages, frameworks
|
||||
- File list with line counts and `fileCategory` per file (`code`, `config`, `docs`, `infra`, `data`, `script`, `markup`)
|
||||
- Complexity estimate
|
||||
- Import map (`importMap`): pre-resolved project-internal imports per file (non-code files have empty arrays)
|
||||
|
||||
Store `importMap` in memory as `$IMPORT_MAP` for use in Phase 2 batch construction.
|
||||
Store the file list as `$FILE_LIST` with `fileCategory` metadata for use in Phase 2 batch construction.
|
||||
|
||||
**Gate check:** If >100 files, inform the user and suggest scoping with a subdirectory argument. Proceed only if user confirms or add guidance that this may take a while.
|
||||
|
||||
If the scan result includes `filteredByIgnore > 0`, report:
|
||||
> Excluded {filteredByIgnore} files via `.understandignore`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1.5 — BATCH
|
||||
|
||||
Report: `[Phase 1.5/7] Computing semantic batches...`
|
||||
|
||||
Run the bundled batching script:
|
||||
```bash
|
||||
node <SKILL_DIR>/compute-batches.mjs $PROJECT_ROOT
|
||||
```
|
||||
|
||||
Reads `.understand-anything/intermediate/scan-result.json`, writes `.understand-anything/intermediate/batches.json`.
|
||||
|
||||
Capture stderr. Append any line starting with `Warning:` to `$PHASE_WARNINGS` for the final report.
|
||||
|
||||
If the script exits non-zero, the failure is hard — relay the full stderr to the user as a Phase 1.5 failure. Do not attempt to recover; the script's internal fallback (count-based) already handles recoverable issues. A non-zero exit means a fundamental problem (missing input file, malformed JSON, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — ANALYZE
|
||||
|
||||
### Full analysis path
|
||||
|
||||
Load `.understand-anything/intermediate/batches.json` (produced by Phase 1.5). Iterate the `batches[]` array.
|
||||
|
||||
Report: `[Phase 2/7] Analyzing files — <totalFiles> files in <totalBatches> batches (up to 5 concurrent)...`
|
||||
|
||||
For each batch, dispatch a subagent using the `file-analyzer` agent definition (at `agents/file-analyzer.md`). Run up to **5 subagents concurrently**. Append the following additional context:
|
||||
|
||||
> **Additional context from main session:**
|
||||
>
|
||||
> Project: `<projectName>` — `<projectDescription>`
|
||||
> Languages: `<languages from Phase 1>`
|
||||
>
|
||||
> $LANGUAGE_DIRECTIVE
|
||||
|
||||
Dispatch prompt template (fill in batch-specific values from `batches.json[i]`):
|
||||
|
||||
> Analyze these files and produce GraphNode and GraphEdge objects.
|
||||
> Project root: `$PROJECT_ROOT`
|
||||
> Project: `<projectName>`
|
||||
> Languages: `<languages>`
|
||||
> Batch: `<batchIndex>/<totalBatches>`
|
||||
> Skill directory (for bundled scripts): `<SKILL_DIR>`
|
||||
> Output: write to `$PROJECT_ROOT/.understand-anything/intermediate/batch-<batchIndex>.json` (single-file mode) OR `batch-<batchIndex>-part-<k>.json` (split mode, per Step B of your output protocol).
|
||||
>
|
||||
> Pre-resolved import data for this batch (use directly — do NOT re-resolve imports from source):
|
||||
> ```json
|
||||
> <batchImportData JSON from batches.json[i].batchImportData>
|
||||
> ```
|
||||
>
|
||||
> Cross-batch neighbors with their exported symbols (confidence boost for cross-batch edges):
|
||||
> ```json
|
||||
> <neighborMap JSON from batches.json[i].neighborMap>
|
||||
> ```
|
||||
>
|
||||
> Files to analyze in this batch (every entry MUST be passed through to `batchFiles` with all four fields — `path`, `language`, `sizeLines`, `fileCategory`):
|
||||
> 1. `<path>` (<sizeLines> lines, language: `<language>`, fileCategory: `<fileCategory>`)
|
||||
> 2. `<path>` (<sizeLines> lines, language: `<language>`, fileCategory: `<fileCategory>`)
|
||||
> ...
|
||||
|
||||
**Output naming is per-batchIndex — no fusion.** If you fuse multiple small batches into a single file-analyzer dispatch for token efficiency, the dispatched agent must STILL write one output file per original `batchIndex` using `batch-<batchIndex>.json` or `batch-<batchIndex>-part-<k>.json`. The merge script's regex (`batch-(\d+)(?:-part-(\d+))?\.json`) silently drops any other naming (e.g., `batch-fused-8-13.json`, `batch-8-13.json`), losing every node and edge in that file. After each dispatch returns, verify each `batchIndex` in the dispatched input has a corresponding `batch-<batchIndex>.json` (or `batch-<batchIndex>-part-*.json`) on disk before proceeding to the next dispatch.
|
||||
|
||||
After ALL batches complete, report to the user: `Phase 2 complete. All <totalBatches> batches analyzed.`
|
||||
|
||||
Run the merge-and-normalize script bundled with this skill (located next to this SKILL.md file — use the skill directory path, not the project root):
|
||||
```bash
|
||||
python <SKILL_DIR>/merge-batch-graphs.py $PROJECT_ROOT
|
||||
```
|
||||
|
||||
This script reads all `batch-*.json` files (including `batch-<i>-part-<k>.json` produced by file-analyzers that split their output) from `$PROJECT_ROOT/.understand-anything/intermediate/`, then in one pass:
|
||||
- Combines all nodes and edges across batches
|
||||
- Normalizes node IDs (strips double prefixes, project-name prefixes, adds missing prefixes)
|
||||
- Normalizes complexity values (`low`→`simple`, `medium`→`moderate`, `high`→`complex`, etc.)
|
||||
- Rewrites edge references to match corrected node IDs
|
||||
- Deduplicates nodes by ID (keeps last occurrence) and edges by `(source, target, type)`
|
||||
- Drops dangling edges referencing missing nodes
|
||||
- Logs all corrections and dropped items to stderr
|
||||
|
||||
The merge script also runs a `tested_by` linker that canonicalizes test-coverage edges in two passes. **Pass 1** walks LLM-emitted `tested_by` edges and flips inverted ones in place; semantically broken edges (test↔test, prod↔prod, orphan endpoints) are dropped. **Pass 2** supplements with path-convention pairings. Production nodes that end up sourcing any `tested_by` edge get a `"tested"` tag. All resulting edges run `production → test`.
|
||||
|
||||
Output: `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`
|
||||
|
||||
Include the script's warnings in `$PHASE_WARNINGS` for the reviewer.
|
||||
|
||||
### Incremental update path
|
||||
|
||||
Write the changed-files list (one path per line) to a temp file:
|
||||
```bash
|
||||
git diff <lastCommitHash>..HEAD --name-only > $PROJECT_ROOT/.understand-anything/tmp/changed-files.txt
|
||||
```
|
||||
|
||||
Run compute-batches with `--changed-files`:
|
||||
```bash
|
||||
node <SKILL_DIR>/compute-batches.mjs $PROJECT_ROOT \
|
||||
--changed-files=$PROJECT_ROOT/.understand-anything/tmp/changed-files.txt
|
||||
```
|
||||
|
||||
This produces a `batches.json` that contains only batches with changed files, but neighborMap entries still reference unchanged files (with their full-graph batchIndex) so cross-batch edges remain emittable.
|
||||
|
||||
Then dispatch file-analyzer subagents per the same template as the full path.
|
||||
|
||||
After batches complete:
|
||||
1. Remove old nodes whose `filePath` matches any changed file from the existing graph
|
||||
2. Remove old edges whose `source` or `target` references a removed node
|
||||
3. Write the pruned existing nodes/edges as `batch-existing.json` in the intermediate directory
|
||||
4. Run the same merge script — it will combine `batch-existing.json` with the fresh `batch-*.json` files:
|
||||
```bash
|
||||
python <SKILL_DIR>/merge-batch-graphs.py $PROJECT_ROOT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — ASSEMBLE REVIEW
|
||||
|
||||
Report to the user: `[Phase 3/7] Reviewing assembled graph...`
|
||||
|
||||
Dispatch a subagent using the `assemble-reviewer` agent definition (at `agents/assemble-reviewer.md`).
|
||||
|
||||
Pass these parameters in the dispatch prompt:
|
||||
|
||||
> Review the assembled graph at `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`.
|
||||
> Project root: `$PROJECT_ROOT`
|
||||
> Batch files are at: `$PROJECT_ROOT/.understand-anything/intermediate/batch-*.json`
|
||||
> Write review output to: `$PROJECT_ROOT/.understand-anything/intermediate/assemble-review.json`
|
||||
>
|
||||
> **Merge script report:**
|
||||
> ```
|
||||
> <paste the full stderr output from merge-batch-graphs.py>
|
||||
> ```
|
||||
>
|
||||
> **Import map for cross-batch edge verification:**
|
||||
> ```json
|
||||
> $IMPORT_MAP
|
||||
> ```
|
||||
|
||||
After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/assemble-review.json` and add any notes to `$PHASE_WARNINGS`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — ARCHITECTURE
|
||||
|
||||
Report to the user: `[Phase 4/7] Identifying architectural layers...`
|
||||
|
||||
**Build the combined prompt template:**
|
||||
1. Use the `architecture-analyzer` agent definition (at `agents/architecture-analyzer.md`).
|
||||
2. **Language context injection:** For each language detected in Phase 1 (e.g., `python`, `markdown`, `dockerfile`, `yaml`, `sql`, `terraform`, `graphql`, `protobuf`, `shell`, `html`, `css`), read the file at `./languages/<language-id>.md` (e.g., `./languages/python.md`, `./languages/dockerfile.md`) and append its content after the base template under a `## Language Context` header. If the file does not exist for a detected language, skip it silently and continue. These files are in the `languages/` subdirectory next to this SKILL.md file. **Include non-code language snippets** — they provide edge patterns and summary styles for non-code files.
|
||||
3. **Framework addendum injection:** For each framework detected in Phase 1 (e.g., `Django`), read the file at `./frameworks/<framework-id-lowercase>.md` (e.g., `./frameworks/django.md`) and append its full content after the language context. If the file does not exist for a detected framework, skip it silently and continue. These files are in the `frameworks/` subdirectory next to this SKILL.md file.
|
||||
4. **Output locale injection:** If `$OUTPUT_LANGUAGE` is NOT `en` (English), read the locale guidance file at `./locales/<language-code>.md` (e.g., `./locales/zh.md`, `./locales/ja.md`, `./locales/ko.md`) and append its content after the framework addendums under a `## Output Language Guidelines` header. This provides language-specific guidance for tag naming conventions, summary style, and layer name translations. If the locale file does not exist for the specified language, skip silently — the `$LANGUAGE_DIRECTIVE` still applies. These files are in the `locales/` subdirectory next to this SKILL.md file.
|
||||
|
||||
Append the language/framework context and the following additional context to the agent's prompt:
|
||||
|
||||
> **Additional context from main session:**
|
||||
>
|
||||
> Frameworks detected: `<frameworks from Phase 1>`
|
||||
>
|
||||
> Directory tree (top 2 levels):
|
||||
> ```
|
||||
> $DIR_TREE
|
||||
> ```
|
||||
>
|
||||
> Use the directory tree, language context, and framework addendums (appended above) to inform layer assignments. Directory structure is strong evidence for layer boundaries. Non-code files (config, docs, infrastructure, data) should be assigned to appropriate layers — see the prompt template for guidance.
|
||||
>
|
||||
> $LANGUAGE_DIRECTIVE
|
||||
|
||||
Pass these parameters in the dispatch prompt:
|
||||
|
||||
> Analyze this codebase's structure to identify architectural layers.
|
||||
> Project root: `$PROJECT_ROOT`
|
||||
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/layers.json`
|
||||
> Project: `<projectName>` — `<projectDescription>`
|
||||
>
|
||||
> File nodes (all node types — includes code files, config, document, service, pipeline, table, schema, resource, endpoint):
|
||||
> ```json
|
||||
> [list of {id, type, name, filePath, summary, tags} for ALL file-level nodes — omit complexity, languageNotes]
|
||||
> ```
|
||||
>
|
||||
> Import edges:
|
||||
> ```json
|
||||
> [list of edges with type "imports"]
|
||||
> ```
|
||||
>
|
||||
> All edges (for cross-category analysis — includes configures, documents, deploys, triggers, etc.):
|
||||
> ```json
|
||||
> [list of ALL edges — include all edge types]
|
||||
> ```
|
||||
|
||||
After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/layers.json` and normalize it into a final `layers` array. Apply these steps **in order**:
|
||||
|
||||
1. **Unwrap envelope:** If the file contains `{ "layers": [...] }` instead of a plain array, extract the inner array. (The prompt requests a plain array, but LLMs may still produce an envelope.)
|
||||
2. **Rename legacy fields:** If any layer object has a `nodes` field instead of `nodeIds`, rename `nodes` → `nodeIds`. If `nodes` entries are objects with an `id` field rather than plain strings, extract just the `id` values into `nodeIds`.
|
||||
3. **Synthesize missing IDs:** If any layer is missing an `id`, generate one as `layer:<kebab-case-name>`.
|
||||
4. **Convert file paths:** If `nodeIds` entries are raw file paths without a known prefix (`file:`, `config:`, `document:`, `service:`, `pipeline:`, `table:`, `schema:`, `resource:`, `endpoint:`), convert them to `file:<relative-path>`.
|
||||
5. **Drop dangling refs:** Remove any `nodeIds` entries that do not exist in the merged node set.
|
||||
|
||||
Each element of the final `layers` array MUST have this shape:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "layer:<kebab-case-name>",
|
||||
"name": "<layer name>",
|
||||
"description": "<what belongs in this layer>",
|
||||
"nodeIds": ["file:src/App.tsx", "config:tsconfig.json", "document:README.md"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
All four fields (`id`, `name`, `description`, `nodeIds`) are required.
|
||||
|
||||
**For incremental updates:** Always re-run architecture analysis on the full merged node set, since layer assignments may shift when files change.
|
||||
|
||||
**Context for incremental updates:** When re-running architecture analysis, also inject the previous layer definitions:
|
||||
|
||||
> Previous layer definitions (for naming consistency):
|
||||
> ```json
|
||||
> [previous layers from existing graph]
|
||||
> ```
|
||||
>
|
||||
> Maintain the same layer names and IDs where possible. Only add/remove layers if the file structure has materially changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — TOUR
|
||||
|
||||
Report to the user: `[Phase 5/7] Building guided tour...`
|
||||
|
||||
Dispatch a subagent using the `tour-builder` agent definition (at `agents/tour-builder.md`). Append the following additional context:
|
||||
|
||||
> **Additional context from main session:**
|
||||
>
|
||||
> Project README (first 3000 chars):
|
||||
> ```
|
||||
> $README_CONTENT
|
||||
> ```
|
||||
>
|
||||
> Project entry point: `$ENTRY_POINT`
|
||||
>
|
||||
> Use the README to align the tour narrative with the project's own documentation. Start the tour from the entry point if one was detected. The tour should tell the same story the README tells, but through the lens of actual code structure.
|
||||
>
|
||||
> $LANGUAGE_DIRECTIVE
|
||||
|
||||
Pass these parameters in the dispatch prompt:
|
||||
|
||||
> Create a guided learning tour for this codebase.
|
||||
> Project root: `$PROJECT_ROOT`
|
||||
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/tour.json`
|
||||
> Project: `<projectName>` — `<projectDescription>`
|
||||
> Languages: `<languages>`
|
||||
>
|
||||
> Nodes (all file-level nodes — includes code files, config, document, service, pipeline, table, schema, resource, endpoint):
|
||||
> ```json
|
||||
> [list of {id, name, filePath, summary, type} for ALL file-level nodes — do NOT include function or class nodes]
|
||||
> ```
|
||||
>
|
||||
> Layers:
|
||||
> ```json
|
||||
> [list of {id, name, description} for each layer — omit nodeIds]
|
||||
> ```
|
||||
>
|
||||
> Edges (all types — includes imports, calls, configures, documents, deploys, triggers, etc.):
|
||||
> ```json
|
||||
> [list of ALL edges — include all edge types for complete graph topology analysis]
|
||||
> ```
|
||||
|
||||
After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/tour.json` and normalize it into a final `tour` array. Apply these steps **in order**:
|
||||
|
||||
1. **Unwrap envelope:** If the file contains `{ "steps": [...] }` instead of a plain array, extract the inner array. (The prompt requests a plain array, but LLMs may still produce an envelope.)
|
||||
2. **Rename legacy fields:** If any step has `nodesToInspect` instead of `nodeIds`, rename it → `nodeIds`. If any step has `whyItMatters` instead of `description`, rename it → `description`.
|
||||
3. **Convert file paths:** If `nodeIds` entries are raw file paths without a known prefix (`file:`, `config:`, `document:`, `service:`, `pipeline:`, `table:`, `schema:`, `resource:`, `endpoint:`), convert them to `file:<relative-path>`.
|
||||
4. **Drop dangling refs:** Remove any `nodeIds` entries that do not exist in the merged node set.
|
||||
5. **Sort** by `order` before saving.
|
||||
|
||||
Each element of the final `tour` array MUST have this shape:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"order": 1,
|
||||
"title": "Project Overview",
|
||||
"description": "Start with the README to understand the project's purpose and architecture.",
|
||||
"nodeIds": ["document:README.md"]
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"title": "Application Entry Point",
|
||||
"description": "This step explains how the frontend boots and mounts.",
|
||||
"nodeIds": ["file:src/main.tsx", "file:src/App.tsx"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Required fields: `order`, `title`, `description`, `nodeIds`. Preserve optional `languageLesson` when present.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — REVIEW
|
||||
|
||||
Report to the user: `[Phase 6/7] Validating knowledge graph...`
|
||||
|
||||
Assemble the full KnowledgeGraph JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"project": {
|
||||
"name": "<projectName>",
|
||||
"languages": ["<languages>"],
|
||||
"frameworks": ["<frameworks>"],
|
||||
"description": "<projectDescription>",
|
||||
"analyzedAt": "<ISO 8601 timestamp>",
|
||||
"gitCommitHash": "<commit hash from Phase 0>"
|
||||
},
|
||||
"nodes": [<all nodes from assembled-graph.json after Phase 3 review>],
|
||||
"edges": [<all edges from assembled-graph.json after Phase 3 review>],
|
||||
"layers": [<layers from Phase 4>],
|
||||
"tour": [<steps from Phase 5>]
|
||||
}
|
||||
```
|
||||
|
||||
1. Before writing the assembled graph, validate that:
|
||||
- `layers` is an array of objects with these required fields: `id`, `name`, `description`, `nodeIds`
|
||||
- `tour` is an array of objects with these required fields: `order`, `title`, `description`, `nodeIds`
|
||||
- `tour[*].languageLesson` is allowed as an optional string field
|
||||
- Every `layers[*].nodeIds` entry exists in the merged node set
|
||||
- Every `tour[*].nodeIds` entry exists in the merged node set
|
||||
|
||||
If validation fails, automatically normalize and rewrite the graph into this shape before saving. If the graph still fails final validation after the normalization pass, save it with warnings but mark dashboard auto-launch as skipped.
|
||||
|
||||
2. Write the assembled graph to `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`.
|
||||
|
||||
3. **Check `$ARGUMENTS` for `--review` flag.** Then run the appropriate validation path:
|
||||
|
||||
---
|
||||
|
||||
#### Default path (no `--review`): inline deterministic validation
|
||||
|
||||
Write the following Node.js script to `$PROJECT_ROOT/.understand-anything/tmp/ua-inline-validate.cjs`:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const graphPath = process.argv[2];
|
||||
const outputPath = process.argv[3];
|
||||
try {
|
||||
const graph = JSON.parse(fs.readFileSync(graphPath, 'utf8'));
|
||||
const issues = [], warnings = [];
|
||||
if (!Array.isArray(graph.nodes)) { issues.push('graph.nodes is missing or not an array'); graph.nodes = []; }
|
||||
if (!Array.isArray(graph.edges)) { issues.push('graph.edges is missing or not an array'); graph.edges = []; }
|
||||
const nodeIds = new Set();
|
||||
const seen = new Map();
|
||||
graph.nodes.forEach((n, i) => {
|
||||
if (!n.id) { issues.push(`Node[${i}] missing id`); return; }
|
||||
if (!n.type) issues.push(`Node[${i}] '${n.id}' missing type`);
|
||||
if (!n.name) issues.push(`Node[${i}] '${n.id}' missing name`);
|
||||
if (!n.summary) issues.push(`Node[${i}] '${n.id}' missing summary`);
|
||||
if (!n.tags || !n.tags.length) issues.push(`Node[${i}] '${n.id}' missing tags`);
|
||||
if (seen.has(n.id)) issues.push(`Duplicate node ID '${n.id}' at indices ${seen.get(n.id)} and ${i}`);
|
||||
else seen.set(n.id, i);
|
||||
nodeIds.add(n.id);
|
||||
});
|
||||
graph.edges.forEach((e, i) => {
|
||||
if (!nodeIds.has(e.source)) issues.push(`Edge[${i}] source '${e.source}' not found`);
|
||||
if (!nodeIds.has(e.target)) issues.push(`Edge[${i}] target '${e.target}' not found`);
|
||||
});
|
||||
const fileLevelTypes = new Set(['file', 'config', 'document', 'service', 'pipeline', 'table', 'schema', 'resource', 'endpoint']);
|
||||
const fileNodes = graph.nodes.filter(n => fileLevelTypes.has(n.type)).map(n => n.id);
|
||||
const assigned = new Map();
|
||||
if (!Array.isArray(graph.layers)) { if (graph.layers) warnings.push('graph.layers is not an array'); graph.layers = []; }
|
||||
if (!Array.isArray(graph.tour)) { if (graph.tour) warnings.push('graph.tour is not an array'); graph.tour = []; }
|
||||
graph.layers.forEach(layer => {
|
||||
(layer.nodeIds || []).forEach(id => {
|
||||
if (!nodeIds.has(id)) issues.push(`Layer '${layer.id}' refs missing node '${id}'`);
|
||||
if (assigned.has(id)) issues.push(`Node '${id}' appears in multiple layers`);
|
||||
assigned.set(id, layer.id);
|
||||
});
|
||||
});
|
||||
fileNodes.forEach(id => {
|
||||
if (!assigned.has(id)) issues.push(`File node '${id}' not in any layer`);
|
||||
});
|
||||
graph.tour.forEach((step, i) => {
|
||||
(step.nodeIds || []).forEach(id => {
|
||||
if (!nodeIds.has(id)) issues.push(`Tour step[${i}] refs missing node '${id}'`);
|
||||
});
|
||||
});
|
||||
const withEdges = new Set([
|
||||
...graph.edges.map(e => e.source),
|
||||
...graph.edges.map(e => e.target)
|
||||
]);
|
||||
graph.nodes.forEach(n => {
|
||||
if (!withEdges.has(n.id)) warnings.push(`Node '${n.id}' has no edges (orphan)`);
|
||||
});
|
||||
const stats = {
|
||||
totalNodes: graph.nodes.length,
|
||||
totalEdges: graph.edges.length,
|
||||
totalLayers: graph.layers.length,
|
||||
tourSteps: graph.tour.length,
|
||||
nodeTypes: graph.nodes.reduce((a, n) => { a[n.type] = (a[n.type]||0)+1; return a; }, {}),
|
||||
edgeTypes: graph.edges.reduce((a, e) => { a[e.type] = (a[e.type]||0)+1; return a; }, {})
|
||||
};
|
||||
fs.writeFileSync(outputPath, JSON.stringify({ issues, warnings, stats }, null, 2));
|
||||
process.exit(0);
|
||||
} catch (err) { process.stderr.write(err.message + '\n'); process.exit(1); }
|
||||
```
|
||||
|
||||
Execute it:
|
||||
```bash
|
||||
node $PROJECT_ROOT/.understand-anything/tmp/ua-inline-validate.cjs \
|
||||
"$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json" \
|
||||
"$PROJECT_ROOT/.understand-anything/intermediate/review.json"
|
||||
```
|
||||
|
||||
If the script exits non-zero, read stderr, fix the script, and retry once.
|
||||
|
||||
---
|
||||
|
||||
#### `--review` path: full LLM reviewer
|
||||
|
||||
If `--review` IS in `$ARGUMENTS`, dispatch the LLM graph-reviewer subagent as follows:
|
||||
|
||||
Dispatch a subagent using the `graph-reviewer` agent definition (at `agents/graph-reviewer.md`). Append the following additional context:
|
||||
|
||||
> **Additional context from main session:**
|
||||
>
|
||||
> Phase 1 scan results (file inventory):
|
||||
> ```json
|
||||
> [list of {path, sizeLines} from scan-result.json]
|
||||
> ```
|
||||
>
|
||||
> Phase warnings/errors accumulated during analysis:
|
||||
> - [list any batch failures, skipped files, or warnings from Phases 2-5]
|
||||
>
|
||||
> Cross-validate: every file in the scan inventory should have a corresponding node in the graph (node types may vary: `file:`, `config:`, `document:`, `service:`, `pipeline:`, `table:`, `schema:`, `resource:`, `endpoint:`). Flag any missing files. Also flag any graph nodes whose `filePath` doesn't appear in the scan inventory.
|
||||
|
||||
Pass these parameters in the dispatch prompt:
|
||||
|
||||
> Validate the knowledge graph at `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`.
|
||||
> Project root: `$PROJECT_ROOT`
|
||||
> Read the file and validate it for completeness and correctness.
|
||||
> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/review.json`
|
||||
|
||||
---
|
||||
|
||||
4. Read `$PROJECT_ROOT/.understand-anything/intermediate/review.json`.
|
||||
|
||||
5. **If `issues` array is non-empty:**
|
||||
- Review the `issues` list
|
||||
- Apply automated fixes where possible:
|
||||
- Remove edges with dangling references
|
||||
- Fill missing required fields with sensible defaults (e.g., empty `tags` -> `["untagged"]`, empty `summary` -> `"No summary available"`)
|
||||
- Remove nodes with invalid types
|
||||
- Re-run the final graph validation after automated fixes
|
||||
- If critical issues remain after one fix attempt, save the graph anyway but include the warnings in the final report and mark dashboard auto-launch as skipped
|
||||
|
||||
6. **If `issues` array is empty:** Proceed to Phase 7.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — SAVE
|
||||
|
||||
Report to the user: `[Phase 7/7] Saving knowledge graph...`
|
||||
|
||||
1. Write the final knowledge graph to `$PROJECT_ROOT/.understand-anything/knowledge-graph.json`.
|
||||
|
||||
2. **Generate structural fingerprints baseline.** This creates the basis for future automatic incremental updates and **must succeed before `meta.json` is written** — otherwise auto-update sees a fresh commit hash with no fingerprints to compare against, classifies every file as STRUCTURAL, and escalates to `FULL_UPDATE` on every subsequent commit (issue #152).
|
||||
|
||||
Write the input file:
|
||||
```bash
|
||||
cat > $PROJECT_ROOT/.understand-anything/intermediate/fingerprint-input.json <<EOF
|
||||
{
|
||||
"projectRoot": "$PROJECT_ROOT",
|
||||
"sourceFilePaths": [<all source file paths from Phase 1, as JSON array>],
|
||||
"gitCommitHash": "<current commit hash>"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Then invoke the bundled script (located next to this SKILL.md):
|
||||
```bash
|
||||
node <SKILL_DIR>/build-fingerprints.mjs \
|
||||
$PROJECT_ROOT/.understand-anything/intermediate/fingerprint-input.json
|
||||
```
|
||||
|
||||
The script uses `TreeSitterPlugin + PluginRegistry` exactly like `extract-structure.mjs`, so the baseline matches the comparison logic used during auto-updates.
|
||||
|
||||
**If the script exits non-zero or stdout does not include `Fingerprints baseline:`, abort Phase 7 and report the error. Do NOT proceed to step 3 (writing `meta.json`).**
|
||||
|
||||
3. Write metadata to `$PROJECT_ROOT/.understand-anything/meta.json` (only after step 2 succeeded):
|
||||
```json
|
||||
{
|
||||
"lastAnalyzedAt": "<ISO 8601 timestamp>",
|
||||
"gitCommitHash": "<commit hash>",
|
||||
"version": "1.0.0",
|
||||
"analyzedFiles": <number of files analyzed>
|
||||
}
|
||||
```
|
||||
|
||||
4. Clean up intermediate files, **preserving `scan-result.json`** so future incremental runs can skip Phase 1 SCAN (see issue #293):
|
||||
```bash
|
||||
# Preserve scan-result.json — Phase 1's deterministic file inventory.
|
||||
# Future incremental runs (Phase 2 compute-batches.mjs --changed-files=…)
|
||||
# need this inventory; without it, Phase 1 must re-dispatch and pay ~157k
|
||||
# tokens / ~158s per incremental run.
|
||||
INTER="$PROJECT_ROOT/.understand-anything/intermediate"
|
||||
if [ -d "$INTER" ]; then
|
||||
find "$INTER" -mindepth 1 -maxdepth 1 -not -name 'scan-result.json' -exec rm -rf {} +
|
||||
fi
|
||||
rm -rf $PROJECT_ROOT/.understand-anything/tmp
|
||||
```
|
||||
|
||||
5. Report a summary to the user containing:
|
||||
- Project name and description
|
||||
- Files analyzed / total files (with breakdown by fileCategory: code, config, docs, infra, data, script, markup)
|
||||
- Nodes created (broken down by type: file, function, class, config, document, service, table, endpoint, pipeline, schema, resource)
|
||||
- Edges created (broken down by type)
|
||||
- Layers identified (with names)
|
||||
- Tour steps generated (count)
|
||||
- Any warnings from the reviewer
|
||||
- Path to the output file: `$PROJECT_ROOT/.understand-anything/knowledge-graph.json`
|
||||
|
||||
6. Only automatically launch the dashboard by invoking the `/understand-dashboard` skill if final graph validation passed after normalization/review fixes.
|
||||
If final validation did not pass, report that the graph was saved with warnings and dashboard launch was skipped.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
- If any subagent dispatch fails, retry **once** with the same prompt plus additional context about the failure.
|
||||
- Track all warnings and errors from each phase in a `$PHASE_WARNINGS` list. When using `--review`, pass this list to the graph-reviewer in Phase 6. On the default path, include accumulated warnings in the Phase 7 final report.
|
||||
- If it fails a second time, skip that phase and continue with partial results.
|
||||
- ALWAYS save partial results — a partial graph is better than no graph.
|
||||
- Report any skipped phases or errors in the final summary so the user knows what happened.
|
||||
- NEVER silently drop errors. Every failure must be visible in the final report.
|
||||
|
||||
---
|
||||
|
||||
## Reference: KnowledgeGraph Schema
|
||||
|
||||
### Node Types (13 total)
|
||||
| Type | Description | ID Convention |
|
||||
|---|---|---|
|
||||
| `file` | Source code file | `file:<relative-path>` |
|
||||
| `function` | Function or method | `function:<relative-path>:<name>` |
|
||||
| `class` | Class, interface, or type | `class:<relative-path>:<name>` |
|
||||
| `module` | Logical module or package | `module:<name>` |
|
||||
| `concept` | Abstract concept or pattern | `concept:<name>` |
|
||||
| `config` | Configuration file (YAML, JSON, TOML, env) | `config:<relative-path>` |
|
||||
| `document` | Documentation file (Markdown, RST, TXT) | `document:<relative-path>` |
|
||||
| `service` | Deployable service definition (Dockerfile, K8s) | `service:<relative-path>` |
|
||||
| `table` | Database table or migration | `table:<relative-path>:<table-name>` |
|
||||
| `endpoint` | API endpoint or route definition | `endpoint:<relative-path>:<endpoint-name>` |
|
||||
| `pipeline` | CI/CD pipeline configuration | `pipeline:<relative-path>` |
|
||||
| `schema` | Schema definition (GraphQL, Protobuf, Prisma) | `schema:<relative-path>` |
|
||||
| `resource` | Infrastructure resource (Terraform, CloudFormation) | `resource:<relative-path>` |
|
||||
|
||||
### Edge Types (26 total)
|
||||
| Category | Types |
|
||||
|---|---|
|
||||
| Structural | `imports`, `exports`, `contains`, `inherits`, `implements` |
|
||||
| Behavioral | `calls`, `subscribes`, `publishes`, `middleware` |
|
||||
| Data flow | `reads_from`, `writes_to`, `transforms`, `validates` |
|
||||
| Dependencies | `depends_on`, `tested_by`, `configures` |
|
||||
| Semantic | `related`, `similar_to` |
|
||||
| Infrastructure | `deploys`, `serves`, `provisions`, `triggers` |
|
||||
| Schema/Data | `migrates`, `documents`, `routes`, `defines_schema` |
|
||||
|
||||
### Edge Weight Conventions
|
||||
| Edge Type | Weight |
|
||||
|---|---|
|
||||
| `contains` | 1.0 |
|
||||
| `inherits`, `implements` | 0.9 |
|
||||
| `calls`, `exports`, `defines_schema` | 0.8 |
|
||||
| `imports`, `deploys`, `migrates` | 0.7 |
|
||||
| `depends_on`, `configures`, `triggers` | 0.6 |
|
||||
| `tested_by`, `documents`, `provisions`, `serves`, `routes` | 0.5 |
|
||||
| All others | 0.5 (default) |
|
||||
90
.qoder/skills/understand/build-fingerprints.mjs
Normal file
90
.qoder/skills/understand/build-fingerprints.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* build-fingerprints.mjs
|
||||
*
|
||||
* Builds the structural-fingerprint baseline used by auto-update's
|
||||
* incremental change detection. Runs once per /understand full rebuild
|
||||
* (Phase 7 step 2.5), generating .understand-anything/fingerprints.json.
|
||||
*
|
||||
* Replaces the LLM-written fingerprint script that previously sat in
|
||||
* SKILL.md as a code example — that example had the wrong signature
|
||||
* for buildFingerprintStore() and never successfully produced a baseline,
|
||||
* which silently broke auto-update for every install (see issue #152).
|
||||
*
|
||||
* Usage:
|
||||
* node build-fingerprints.mjs <input.json>
|
||||
*
|
||||
* Input JSON:
|
||||
* { projectRoot: string, sourceFilePaths: string[], gitCommitHash: string }
|
||||
*
|
||||
* Writes: <projectRoot>/.understand-anything/fingerprints.json
|
||||
* Exit code: 0 on success (including 0 files analyzed); non-zero on error.
|
||||
*/
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
// skills/understand/ -> plugin root is two dirs up
|
||||
const pluginRoot = resolve(__dirname, '../..');
|
||||
const require = createRequire(resolve(pluginRoot, 'package.json'));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resolve @understand-anything/core (matches extract-structure.mjs).
|
||||
// pathToFileURL() is required for Windows: dynamic import() of a raw
|
||||
// "C:\..." path throws ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
||||
// ---------------------------------------------------------------------------
|
||||
let core;
|
||||
try {
|
||||
core = await import(pathToFileURL(require.resolve('@understand-anything/core')).href);
|
||||
} catch {
|
||||
core = await import(pathToFileURL(resolve(pluginRoot, 'packages/core/dist/index.js')).href);
|
||||
}
|
||||
|
||||
const {
|
||||
TreeSitterPlugin,
|
||||
PluginRegistry,
|
||||
builtinLanguageConfigs,
|
||||
registerAllParsers,
|
||||
buildFingerprintStore,
|
||||
saveFingerprints,
|
||||
} = core;
|
||||
|
||||
async function main() {
|
||||
const [, , inputPath] = process.argv;
|
||||
if (!inputPath) {
|
||||
process.stderr.write('Usage: node build-fingerprints.mjs <input.json>\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { projectRoot, sourceFilePaths, gitCommitHash } = JSON.parse(
|
||||
readFileSync(inputPath, 'utf-8'),
|
||||
);
|
||||
|
||||
if (!projectRoot || !Array.isArray(sourceFilePaths) || typeof gitCommitHash !== 'string') {
|
||||
throw new Error(
|
||||
'Invalid input: requires { projectRoot: string, sourceFilePaths: string[], gitCommitHash: string }',
|
||||
);
|
||||
}
|
||||
|
||||
// Create tree-sitter plugin with all configs that have WASM grammars,
|
||||
// mirroring extract-structure.mjs so the baseline matches the comparison
|
||||
// logic used during auto-updates.
|
||||
const tsConfigs = builtinLanguageConfigs.filter((c) => c.treeSitter);
|
||||
const tsPlugin = new TreeSitterPlugin(tsConfigs);
|
||||
await tsPlugin.init();
|
||||
|
||||
const registry = new PluginRegistry();
|
||||
registry.register(tsPlugin);
|
||||
registerAllParsers(registry);
|
||||
|
||||
const store = buildFingerprintStore(projectRoot, sourceFilePaths, registry, gitCommitHash);
|
||||
saveFingerprints(projectRoot, store);
|
||||
|
||||
const fileCount = Object.keys(store.files).length;
|
||||
process.stdout.write(`Fingerprints baseline: ${fileCount} files\n`);
|
||||
}
|
||||
|
||||
await main();
|
||||
555
.qoder/skills/understand/compute-batches.mjs
Normal file
555
.qoder/skills/understand/compute-batches.mjs
Normal file
@@ -0,0 +1,555 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* compute-batches.mjs — Phase 1.5 of /understand
|
||||
*
|
||||
* Reads scan-result.json, runs Louvain community detection on the import
|
||||
* graph, and writes batches.json containing batches + neighborMap.
|
||||
*
|
||||
* Usage:
|
||||
* node compute-batches.mjs <project-root> [--changed-files=<path>]
|
||||
*
|
||||
* Input: <project-root>/.understand-anything/intermediate/scan-result.json
|
||||
* Output: <project-root>/.understand-anything/intermediate/batches.json
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const PLUGIN_ROOT = resolve(dirname(__filename), '../..');
|
||||
const require = createRequire(resolve(PLUGIN_ROOT, 'package.json'));
|
||||
|
||||
let core;
|
||||
try {
|
||||
core = await import(pathToFileURL(require.resolve('@understand-anything/core')).href);
|
||||
} catch {
|
||||
core = await import(pathToFileURL(resolve(PLUGIN_ROOT, 'packages/core/dist/index.js')).href);
|
||||
}
|
||||
const { TreeSitterPlugin, PluginRegistry, builtinLanguageConfigs, registerAllParsers } = core;
|
||||
|
||||
import Graph from 'graphology';
|
||||
import louvain from 'graphology-communities-louvain';
|
||||
|
||||
/**
|
||||
* For each code file, returns its top-level exported symbol names (functions,
|
||||
* classes, exported consts). Per-file errors are swallowed into [] with a
|
||||
* visible warning so a single bad file does not abort batching.
|
||||
*
|
||||
* Returns Map<path, string[]>.
|
||||
*/
|
||||
async function extractExports(projectRoot, codeFiles) {
|
||||
let registry;
|
||||
try {
|
||||
const tsConfigs = builtinLanguageConfigs.filter(c => c.treeSitter);
|
||||
const tsPlugin = new TreeSitterPlugin(tsConfigs);
|
||||
await tsPlugin.init();
|
||||
registry = new PluginRegistry();
|
||||
registry.register(tsPlugin);
|
||||
registerAllParsers(registry);
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: compute-batches: tree-sitter init failed (${err.message}) ` +
|
||||
`— all symbols=[] in neighborMap — cross-batch edges limited to file-level\n`,
|
||||
);
|
||||
return new Map(codeFiles.map(f => [f.path, []]));
|
||||
}
|
||||
|
||||
const exportsByPath = new Map();
|
||||
for (const file of codeFiles) {
|
||||
const abs = join(projectRoot, file.path);
|
||||
let content;
|
||||
try {
|
||||
content = readFileSync(abs, 'utf-8');
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: compute-batches: exports extraction failed for ${file.path} ` +
|
||||
`(read error: ${err.message}) — symbols=[] in neighborMap — ` +
|
||||
`cross-batch edges to this file limited to file-level\n`,
|
||||
);
|
||||
exportsByPath.set(file.path, []);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const analysis = registry.analyzeFile(file.path, content);
|
||||
const names = (analysis?.exports || []).map(e => e.name).filter(Boolean);
|
||||
exportsByPath.set(file.path, names);
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: compute-batches: exports extraction failed for ${file.path} ` +
|
||||
`(analyze error: ${err.message}) — symbols=[] in neighborMap — ` +
|
||||
`cross-batch edges to this file limited to file-level\n`,
|
||||
);
|
||||
exportsByPath.set(file.path, []);
|
||||
}
|
||||
}
|
||||
return exportsByPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build batches for non-code files per Groups A-E in the design spec.
|
||||
* Returns Array<{ files: FileMeta[], mergeable: boolean }> — caller assigns
|
||||
* batchIndex. `mergeable=false` for semantic Groups A-D (Dockerfile clusters,
|
||||
* .github/workflows, .gitlab-ci/.circleci, SQL migrations) preserves their
|
||||
* boundary intent across the merge-small pass; Group E (catch-all parent-dir
|
||||
* grouping) is `mergeable=true` so its tiny singletons can be pooled.
|
||||
*/
|
||||
function buildNonCodeBatches(nonCodeFiles) {
|
||||
const byPath = new Map(nonCodeFiles.map(f => [f.path, f]));
|
||||
const consumed = new Set();
|
||||
const groups = [];
|
||||
|
||||
const dirOf = p => p.includes('/') ? p.slice(0, p.lastIndexOf('/')) : '';
|
||||
const baseOf = p => p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
|
||||
|
||||
// Group A: per-directory Dockerfile clusters.
|
||||
const dirsWithDockerfile = new Set(
|
||||
[...byPath.keys()]
|
||||
.filter(p => baseOf(p) === 'Dockerfile')
|
||||
.map(dirOf),
|
||||
);
|
||||
for (const dir of [...dirsWithDockerfile].sort()) {
|
||||
const inDir = [...byPath.keys()].filter(p => dirOf(p) === dir);
|
||||
const cluster = inDir.filter(p => {
|
||||
const b = baseOf(p);
|
||||
return b === 'Dockerfile'
|
||||
|| b === '.dockerignore'
|
||||
|| b.startsWith('docker-compose.');
|
||||
});
|
||||
if (cluster.length) {
|
||||
groups.push({ files: cluster.map(p => byPath.get(p)), mergeable: false });
|
||||
cluster.forEach(p => consumed.add(p));
|
||||
}
|
||||
}
|
||||
|
||||
// Group B: .github/workflows/*
|
||||
const ghWorkflows = [...byPath.keys()].filter(
|
||||
p => p.startsWith('.github/workflows/') && (p.endsWith('.yml') || p.endsWith('.yaml')),
|
||||
).filter(p => !consumed.has(p));
|
||||
if (ghWorkflows.length) {
|
||||
groups.push({ files: ghWorkflows.map(p => byPath.get(p)), mergeable: false });
|
||||
ghWorkflows.forEach(p => consumed.add(p));
|
||||
}
|
||||
|
||||
// Group C: .gitlab-ci.yml + .circleci/*
|
||||
const ciFiles = [...byPath.keys()].filter(
|
||||
p => (p === '.gitlab-ci.yml' || p.startsWith('.circleci/'))
|
||||
&& !consumed.has(p),
|
||||
);
|
||||
if (ciFiles.length) {
|
||||
groups.push({ files: ciFiles.map(p => byPath.get(p)), mergeable: false });
|
||||
ciFiles.forEach(p => consumed.add(p));
|
||||
}
|
||||
|
||||
// Group D: SQL migrations per migrations/ or migration/ directory.
|
||||
// Defensive consumed.has check: no upstream group consumes SQL today, but
|
||||
// future Group additions could; keep the check for forward-compat.
|
||||
const migrationDirs = new Set(
|
||||
[...byPath.keys()]
|
||||
.filter(p => p.endsWith('.sql'))
|
||||
.map(dirOf)
|
||||
.filter(d => /(^|\/)migrations?$/.test(d)),
|
||||
);
|
||||
for (const dir of migrationDirs) {
|
||||
const sqls = [...byPath.keys()]
|
||||
.filter(p => dirOf(p) === dir && p.endsWith('.sql') && !consumed.has(p))
|
||||
.sort();
|
||||
if (sqls.length) {
|
||||
groups.push({ files: sqls.map(p => byPath.get(p)), mergeable: false });
|
||||
sqls.forEach(p => consumed.add(p));
|
||||
}
|
||||
}
|
||||
|
||||
// Group E: all remaining grouped by immediate parent dir, max 20 per batch
|
||||
const remainingByDir = new Map();
|
||||
for (const p of [...byPath.keys()].sort()) {
|
||||
if (consumed.has(p)) continue;
|
||||
const dir = dirOf(p);
|
||||
if (!remainingByDir.has(dir)) remainingByDir.set(dir, []);
|
||||
remainingByDir.get(dir).push(p);
|
||||
}
|
||||
// Per design spec: max files per parent-dir batch for Group E.
|
||||
const MAX_E = 20;
|
||||
for (const [, paths] of remainingByDir) {
|
||||
for (let i = 0; i < paths.length; i += MAX_E) {
|
||||
const slice = paths.slice(i, i + MAX_E);
|
||||
groups.push({ files: slice.map(p => byPath.get(p)), mergeable: true });
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lookup map from file path → batchIndex across all batches (code +
|
||||
* non-code). Used to resolve cross-batch neighbor references in neighborMap.
|
||||
*/
|
||||
function buildBatchOfMap(allBatches) {
|
||||
const m = new Map();
|
||||
for (const b of allBatches) {
|
||||
for (const f of b.files) m.set(f.path, b.batchIndex);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Map<path, communityId> via Louvain. May throw — caller must catch
|
||||
* and fall back if it does. Honors UA_COMPUTE_BATCHES_FORCE_LOUVAIN_THROW=1
|
||||
* to allow tests to exercise the fallback path.
|
||||
*/
|
||||
function runLouvain(codeFiles, importMap) {
|
||||
if (process.env.UA_COMPUTE_BATCHES_FORCE_LOUVAIN_THROW === '1') {
|
||||
throw new Error('forced throw via UA_COMPUTE_BATCHES_FORCE_LOUVAIN_THROW');
|
||||
}
|
||||
const g = new Graph({ type: 'undirected', allowSelfLoops: false });
|
||||
for (const f of codeFiles) g.addNode(f.path);
|
||||
for (const [src, targets] of Object.entries(importMap)) {
|
||||
if (!g.hasNode(src)) continue;
|
||||
for (const tgt of targets) {
|
||||
if (!g.hasNode(tgt) || src === tgt || g.hasEdge(src, tgt)) continue;
|
||||
g.addEdge(src, tgt);
|
||||
}
|
||||
}
|
||||
const cs = louvain(g); // { nodeId: communityId }
|
||||
return new Map(Object.entries(cs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Map<path, communityId> via alphabetical chunking of `batchSize`
|
||||
* files per batch. Deterministic, used as fallback when Louvain fails.
|
||||
*/
|
||||
function countBasedAssignment(codeFiles, batchSize = 12) {
|
||||
const out = new Map();
|
||||
const sorted = [...codeFiles].map(f => f.path).sort();
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
out.set(sorted[i], `count_${Math.floor(i / batchSize)}`);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pool small mergeable batches into "misc" batches to reduce dispatch overhead.
|
||||
* Preserves semantic groupings (non-code Groups A-D, marked `mergeable=false`)
|
||||
* regardless of size; only merges code Louvain singletons / orphans and
|
||||
* Group E parent-dir batches that fall below MIN_BATCH_SIZE.
|
||||
*
|
||||
* On a 314-file microservices-demo run, vanilla Louvain produced 87 singleton
|
||||
* communities → 87 dispatch tasks of size 1. This pass collapses them into
|
||||
* ceil(N / MAX_MERGE_TARGET) misc batches, drastically cutting orchestration
|
||||
* overhead while leaving the high-modularity communities untouched.
|
||||
*
|
||||
* Returns the rewritten batch list with reassigned batchIndex (1-based,
|
||||
* keepers first preserving their relative order, misc batches appended).
|
||||
*/
|
||||
function mergeSmallBatches(bareBatches) {
|
||||
// MIN_BATCH_SIZE=3: below this, file-analyzer dispatch overhead (subagent
|
||||
// spin-up, prompt setup) dwarfs the per-file analysis cost — not worth a
|
||||
// standalone batch.
|
||||
const MIN_BATCH_SIZE = 3;
|
||||
// MAX_MERGE_TARGET=25: stays below MAX_COMMUNITY_SIZE=35 so the misc-batch
|
||||
// agent retains headroom for neighborMap context without overflowing.
|
||||
const MAX_MERGE_TARGET = 25;
|
||||
|
||||
const keepers = [];
|
||||
const smallMergeable = [];
|
||||
for (const b of bareBatches) {
|
||||
if (b.mergeable && b.files.length < MIN_BATCH_SIZE) {
|
||||
smallMergeable.push(b);
|
||||
} else {
|
||||
keepers.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
if (smallMergeable.length === 0) {
|
||||
// Nothing to merge — strip mergeable flag and renumber for cleanliness.
|
||||
return keepers.map((b, i) => ({
|
||||
batchIndex: i + 1,
|
||||
files: b.files,
|
||||
}));
|
||||
}
|
||||
|
||||
// Pool and sort deterministically by path so repeated runs match byte-for-byte.
|
||||
const pooledFiles = smallMergeable
|
||||
.flatMap(b => b.files)
|
||||
.sort((a, b) => a.path.localeCompare(b.path));
|
||||
|
||||
const miscBatches = [];
|
||||
for (let i = 0; i < pooledFiles.length; i += MAX_MERGE_TARGET) {
|
||||
miscBatches.push({ files: pooledFiles.slice(i, i + MAX_MERGE_TARGET) });
|
||||
}
|
||||
|
||||
// Use `Info:` rather than `Warning:` — singleton consolidation is a
|
||||
// routine optimization, not a fallback/degrade path. Per
|
||||
// [[feedback_visible_warnings]] only fallbacks should bubble as Warning:
|
||||
// to the Phase 7 final report. Real warnings would get drowned out if
|
||||
// every normal Louvain run with singletons (i.e. almost every run) added
|
||||
// a Warning: line.
|
||||
process.stderr.write(
|
||||
`Info: compute-batches: merged ${smallMergeable.length} small batches ` +
|
||||
`(${pooledFiles.length} files) into ${miscBatches.length} misc batches ` +
|
||||
`— singletons and orphans consolidated\n`,
|
||||
);
|
||||
|
||||
const final = [...keepers, ...miscBatches];
|
||||
return final.map((b, i) => ({
|
||||
batchIndex: i + 1,
|
||||
files: b.files,
|
||||
}));
|
||||
}
|
||||
|
||||
// ── Main: load → Louvain (or count-fallback) → enrich → write batches.json ─
|
||||
async function main() {
|
||||
const projectRoot = process.argv[2];
|
||||
if (!projectRoot) {
|
||||
process.stderr.write('Usage: node compute-batches.mjs <project-root> [--changed-files=<path>]\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let changedFiles = null;
|
||||
for (const arg of process.argv.slice(3)) {
|
||||
const m = arg.match(/^--changed-files=(.+)$/);
|
||||
if (m) {
|
||||
const p = m[1];
|
||||
let content;
|
||||
try {
|
||||
content = readFileSync(p, 'utf-8');
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Error: compute-batches: --changed-files path not readable: ${p} (${err.message})\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const lines = content
|
||||
.split('\n')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
changedFiles = new Set(lines);
|
||||
}
|
||||
}
|
||||
|
||||
const scanPath = join(projectRoot, '.understand-anything', 'intermediate', 'scan-result.json');
|
||||
if (!existsSync(scanPath)) {
|
||||
process.stderr.write(`Error: scan-result.json not found at ${scanPath}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const scan = JSON.parse(readFileSync(scanPath, 'utf-8'));
|
||||
const files = scan.files || [];
|
||||
const codeFiles = files.filter(f => f.fileCategory === 'code');
|
||||
const nonCodeFiles = files.filter(f => f.fileCategory !== 'code');
|
||||
const importMap = scan.importMap || {};
|
||||
|
||||
process.stderr.write(`Loaded ${files.length} files (${codeFiles.length} code).\n`);
|
||||
|
||||
const exportsByPath = await extractExports(projectRoot, codeFiles);
|
||||
|
||||
let algorithm = 'louvain';
|
||||
let perFileCommunity;
|
||||
try {
|
||||
perFileCommunity = runLouvain(codeFiles, importMap);
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: compute-batches: Louvain failed (${err.message}) ` +
|
||||
`— falling back to count-based grouping (12 files/batch) ` +
|
||||
`— module semantic boundaries lost\n`,
|
||||
);
|
||||
perFileCommunity = countBasedAssignment(codeFiles, 12);
|
||||
algorithm = 'count-fallback';
|
||||
}
|
||||
|
||||
// Group files by community id
|
||||
const filesByCommunity = new Map();
|
||||
for (const [path, cid] of perFileCommunity) {
|
||||
if (!filesByCommunity.has(cid)) filesByCommunity.set(cid, []);
|
||||
filesByCommunity.get(cid).push(path);
|
||||
}
|
||||
|
||||
// Size enforcement only on louvain output. count-fallback already chunked.
|
||||
const MAX_COMMUNITY_SIZE = 35;
|
||||
const splitCommunities = new Map();
|
||||
let nextSyntheticId = 0;
|
||||
if (algorithm === 'louvain') {
|
||||
for (const [cid, paths] of filesByCommunity) {
|
||||
if (paths.length <= MAX_COMMUNITY_SIZE) {
|
||||
splitCommunities.set(cid, paths);
|
||||
continue;
|
||||
}
|
||||
process.stderr.write(
|
||||
`Warning: compute-batches: community size ${paths.length} > max ${MAX_COMMUNITY_SIZE} ` +
|
||||
`— splitting via alphabetical chunking — modularity may decrease\n`,
|
||||
);
|
||||
const sorted = [...paths].sort();
|
||||
const parts = Math.ceil(paths.length / MAX_COMMUNITY_SIZE);
|
||||
const perPart = Math.ceil(paths.length / parts);
|
||||
for (let i = 0; i < parts; i++) {
|
||||
const slice = sorted.slice(i * perPart, (i + 1) * perPart);
|
||||
const synthId = `__split_${cid}_${nextSyntheticId++}`;
|
||||
splitCommunities.set(synthId, slice);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [cid, paths] of filesByCommunity) splitCommunities.set(cid, paths);
|
||||
}
|
||||
|
||||
// Sort communities by size desc, then by min-path asc for determinism
|
||||
const sortedCommunities = [...splitCommunities.entries()]
|
||||
.sort((a, b) => {
|
||||
if (b[1].length !== a[1].length) return b[1].length - a[1].length;
|
||||
const minA = [...a[1]].sort()[0];
|
||||
const minB = [...b[1]].sort()[0];
|
||||
return minA.localeCompare(minB);
|
||||
});
|
||||
|
||||
// Build per-batch file list with full file metadata from scan
|
||||
const fileMetaByPath = new Map(files.map(f => [f.path, f]));
|
||||
// Safe: every path in a community is a graph node, and graph nodes are a
|
||||
// subset of files (see addNode loop above). fileMetaByPath.get() can
|
||||
// never return undefined here.
|
||||
|
||||
// First-pass: assemble bare batches (no batchImportData/neighborMap yet).
|
||||
// All Louvain communities are mergeable=true so the merge-small pass can
|
||||
// collapse singletons / 2-file orphans. Non-code groups carry per-group
|
||||
// mergeable flags from buildNonCodeBatches (false for semantic Groups A-D,
|
||||
// true for Group E catch-all).
|
||||
const codeBatchObjsBare = sortedCommunities.map(([, paths], idx) => ({
|
||||
batchIndex: idx + 1,
|
||||
files: paths.sort().map(p => fileMetaByPath.get(p)),
|
||||
mergeable: true,
|
||||
}));
|
||||
const nonCodeGroups = buildNonCodeBatches(nonCodeFiles);
|
||||
const nonCodeBatchObjsBare = nonCodeGroups.map((g, i) => ({
|
||||
batchIndex: codeBatchObjsBare.length + i + 1,
|
||||
files: g.files,
|
||||
mergeable: g.mergeable,
|
||||
}));
|
||||
const bareBatches = [...codeBatchObjsBare, ...nonCodeBatchObjsBare];
|
||||
const mergedBareBatches = mergeSmallBatches(bareBatches);
|
||||
const batchOf = buildBatchOfMap(mergedBareBatches);
|
||||
|
||||
// Build reverse import map: target → [sources that import target]
|
||||
const reverseImportMap = new Map();
|
||||
for (const [src, targets] of Object.entries(importMap)) {
|
||||
for (const tgt of targets) {
|
||||
if (!reverseImportMap.has(tgt)) reverseImportMap.set(tgt, []);
|
||||
reverseImportMap.get(tgt).push(src);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute neighbor degree (number of import relations) per path, used for
|
||||
// truncation when neighborMap[file] has > MAX_NEIGHBORS entries.
|
||||
const NEIGHBOR_DEGREE = new Map();
|
||||
for (const f of codeFiles) {
|
||||
const outDeg = (importMap[f.path] || []).length;
|
||||
const inDeg = (reverseImportMap.get(f.path) || []).length;
|
||||
NEIGHBOR_DEGREE.set(f.path, outDeg + inDeg);
|
||||
}
|
||||
|
||||
const MAX_NEIGHBORS = 50;
|
||||
|
||||
// Second-pass: enrich each batch with batchImportData + neighborMap
|
||||
const batches = mergedBareBatches.map(b => {
|
||||
const batchPaths = new Set(b.files.map(f => f.path));
|
||||
const batchImportData = {};
|
||||
const neighborMap = {};
|
||||
for (const f of b.files) {
|
||||
batchImportData[f.path] = (importMap[f.path] || []).slice();
|
||||
|
||||
// 1-hop neighbors: imports out + imported-by in, excluding same batch.
|
||||
// Note on truncation: we measure "popularity" by total raw 1-hop neighbor
|
||||
// count (rawCount), not kept.length. A widely-imported hub like a logger
|
||||
// module may have N>50 inbound imports but, after Louvain + size
|
||||
// enforcement, only some land in other batches — kept.length can be < 50
|
||||
// while the file is still a high-degree hub whose missing relationships
|
||||
// matter for downstream cross-batch edge confidence. Warning on rawCount
|
||||
// surfaces this; truncation on kept ensures the JSON stays bounded.
|
||||
const outNeighbors = importMap[f.path] || [];
|
||||
const inNeighbors = reverseImportMap.get(f.path) || [];
|
||||
const all = new Set([...outNeighbors, ...inNeighbors]);
|
||||
const rawCount = all.size;
|
||||
const filtered = [...all].filter(p => batchOf.has(p) && !batchPaths.has(p));
|
||||
|
||||
let kept = filtered.map(p => ({
|
||||
path: p,
|
||||
batchIndex: batchOf.get(p),
|
||||
symbols: exportsByPath.get(p) || [],
|
||||
}));
|
||||
|
||||
if (rawCount > MAX_NEIGHBORS) {
|
||||
kept.sort((a, b2) => (NEIGHBOR_DEGREE.get(b2.path) || 0)
|
||||
- (NEIGHBOR_DEGREE.get(a.path) || 0)
|
||||
|| a.path.localeCompare(b2.path)); // deterministic tiebreak
|
||||
const beforeSlice = kept.length;
|
||||
kept = kept.slice(0, MAX_NEIGHBORS);
|
||||
process.stderr.write(
|
||||
`Warning: compute-batches: neighborMap for ${f.path} has high 1-hop degree ${rawCount} ` +
|
||||
`— exceeds soft cap of ${MAX_NEIGHBORS} — keeping top ${kept.length} cross-batch entries ` +
|
||||
`(${beforeSlice - kept.length} dropped by degree sort)\n`,
|
||||
);
|
||||
}
|
||||
|
||||
if (kept.length) neighborMap[f.path] = kept;
|
||||
}
|
||||
return { batchIndex: b.batchIndex, files: b.files, batchImportData, neighborMap };
|
||||
});
|
||||
|
||||
let finalBatches = batches;
|
||||
if (changedFiles) {
|
||||
finalBatches = batches.filter(b => b.files.some(f => changedFiles.has(f.path)));
|
||||
// batchIndex on filtered batches retains the full-graph assignment
|
||||
// (the design says neighborMap should still reference unchanged files'
|
||||
// full-graph batchIndex). No renumbering.
|
||||
}
|
||||
|
||||
// Note: under --changed-files mode, totalFiles is the FULL project file
|
||||
// count (unchanged from the input scan) while totalBatches reflects only
|
||||
// the filtered set written to disk. batchIndex values on the kept batches
|
||||
// preserve the full-graph assignment so neighborMap references resolve.
|
||||
const output = {
|
||||
schemaVersion: 1,
|
||||
algorithm,
|
||||
totalFiles: scan.files.length,
|
||||
totalBatches: finalBatches.length,
|
||||
exportsByPath: Object.fromEntries(exportsByPath),
|
||||
batches: finalBatches,
|
||||
};
|
||||
|
||||
const outPath = join(projectRoot, '.understand-anything', 'intermediate', 'batches.json');
|
||||
writeFileSync(outPath, JSON.stringify(output, null, 2), 'utf-8');
|
||||
const batchSizes = finalBatches.map(b => b.files.length);
|
||||
const maxSize = batchSizes.length ? Math.max(...batchSizes) : 0;
|
||||
const minSize = batchSizes.length ? Math.min(...batchSizes) : 0;
|
||||
process.stderr.write(
|
||||
`Wrote ${finalBatches.length} batches (sizes: max=${maxSize}, min=${minSize}) to ${outPath}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run only when executed directly as a CLI; importing the module (e.g. from
|
||||
// tests) must not trigger main().
|
||||
//
|
||||
// Canonicalize both sides through realpathSync. Node ESM resolves
|
||||
// import.meta.url through symlinks but pathToFileURL(process.argv[1]) preserves
|
||||
// them, so a raw equality check silently no-ops when the script is invoked via
|
||||
// a symlinked plugin install path (the default in Claude Code / Copilot CLI
|
||||
// caches). See GitHub issue #162.
|
||||
// ---------------------------------------------------------------------------
|
||||
function isCliEntry() {
|
||||
if (!process.argv[1]) return false;
|
||||
try {
|
||||
const modulePath = realpathSync(fileURLToPath(import.meta.url));
|
||||
const argvPath = realpathSync(process.argv[1]);
|
||||
return modulePath === argvPath;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCliEntry()) {
|
||||
try {
|
||||
await main();
|
||||
} catch (err) {
|
||||
process.stderr.write(`compute-batches.mjs failed: ${err.message}\n${err.stack}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
1567
.qoder/skills/understand/extract-import-map.mjs
Normal file
1567
.qoder/skills/understand/extract-import-map.mjs
Normal file
File diff suppressed because it is too large
Load Diff
334
.qoder/skills/understand/extract-structure.mjs
Normal file
334
.qoder/skills/understand/extract-structure.mjs
Normal file
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* extract-structure.mjs
|
||||
*
|
||||
* Deterministic structural extraction script for the file-analyzer agent.
|
||||
* Uses PluginRegistry (TreeSitterPlugin + non-code parsers) from @understand-anything/core
|
||||
* to replace the LLM-generated throwaway regex scripts in Phase 1.
|
||||
*
|
||||
* Usage:
|
||||
* node extract-structure.mjs <input.json> <output.json>
|
||||
*
|
||||
* Input JSON:
|
||||
* { projectRoot, batchFiles: [{path, language, sizeLines, fileCategory}], batchImportData }
|
||||
*
|
||||
* Output JSON:
|
||||
* { scriptCompleted, filesAnalyzed, filesSkipped, results: [...] }
|
||||
*/
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, resolve, join } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
// skills/understand/ -> plugin root is two dirs up
|
||||
const pluginRoot = resolve(__dirname, '../..');
|
||||
const require = createRequire(resolve(pluginRoot, 'package.json'));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resolve @understand-anything/core
|
||||
//
|
||||
// Node ESM dynamic import() requires a file:// URL on Windows; passing a raw
|
||||
// absolute path like "C:\..." throws ERR_UNSUPPORTED_ESM_URL_SCHEME because the
|
||||
// loader parses "C:" as a URL scheme. Wrap both resolutions in pathToFileURL().
|
||||
// ---------------------------------------------------------------------------
|
||||
let core;
|
||||
try {
|
||||
core = await import(pathToFileURL(require.resolve('@understand-anything/core')).href);
|
||||
} catch {
|
||||
// Fallback: direct path for installed plugin cache layouts
|
||||
core = await import(pathToFileURL(resolve(pluginRoot, 'packages/core/dist/index.js')).href);
|
||||
}
|
||||
|
||||
const { TreeSitterPlugin, PluginRegistry, builtinLanguageConfigs, registerAllParsers } = core;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
async function main() {
|
||||
const [,, inputPath, outputPath] = process.argv;
|
||||
if (!inputPath || !outputPath) {
|
||||
process.stderr.write('Usage: node extract-structure.mjs <input.json> <output.json>\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read input
|
||||
const inputRaw = readFileSync(inputPath, 'utf-8');
|
||||
const input = JSON.parse(inputRaw);
|
||||
const { projectRoot, batchFiles, batchImportData } = input;
|
||||
|
||||
if (!projectRoot || !Array.isArray(batchFiles)) {
|
||||
throw new Error('Invalid input: must contain projectRoot and batchFiles array');
|
||||
}
|
||||
|
||||
// Create tree-sitter plugin with all configs that have WASM grammars
|
||||
const tsConfigs = builtinLanguageConfigs.filter(c => c.treeSitter);
|
||||
const tsPlugin = new TreeSitterPlugin(tsConfigs);
|
||||
await tsPlugin.init();
|
||||
|
||||
// Create registry and register tree-sitter + all non-code parsers
|
||||
const registry = new PluginRegistry();
|
||||
registry.register(tsPlugin);
|
||||
registerAllParsers(registry);
|
||||
|
||||
const results = [];
|
||||
const filesSkipped = [];
|
||||
|
||||
for (const file of batchFiles) {
|
||||
const absolutePath = join(projectRoot, file.path);
|
||||
|
||||
// Read file content
|
||||
let content;
|
||||
try {
|
||||
content = readFileSync(absolutePath, 'utf-8');
|
||||
} catch {
|
||||
filesSkipped.push(file.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Line counts. POSIX text files end in a trailing newline, which makes
|
||||
// `split('\n')` produce one extra empty element. Match `wc -l` semantics
|
||||
// (used by the project scanner for `sizeLines`) so the two counts agree.
|
||||
const lines = content.split('\n');
|
||||
const totalLines = content.endsWith('\n') ? Math.max(0, lines.length - 1) : lines.length;
|
||||
const nonEmptyLines = lines.filter(l => l.trim().length > 0).length;
|
||||
|
||||
// Structural analysis via registry
|
||||
let analysis = null;
|
||||
try {
|
||||
analysis = registry.analyzeFile(file.path, content);
|
||||
} catch {
|
||||
// If analysis throws, treat as degraded — still include basic metrics
|
||||
}
|
||||
|
||||
// Call graph extraction (code files only)
|
||||
let callGraph = null;
|
||||
if (file.fileCategory === 'code' || file.fileCategory === 'script') {
|
||||
try {
|
||||
const cg = registry.extractCallGraph(file.path, content);
|
||||
if (cg && cg.length > 0) {
|
||||
callGraph = cg.map(entry => ({
|
||||
caller: entry.caller,
|
||||
callee: entry.callee,
|
||||
lineNumber: entry.lineNumber,
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// Call graph extraction failed — non-fatal
|
||||
}
|
||||
}
|
||||
|
||||
// Build result object
|
||||
const result = buildResult(file, totalLines, nonEmptyLines, analysis, callGraph, batchImportData);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// Write output
|
||||
const output = {
|
||||
scriptCompleted: true,
|
||||
filesAnalyzed: results.length,
|
||||
filesSkipped,
|
||||
results,
|
||||
};
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf-8');
|
||||
|
||||
if (!existsSync(outputPath)) {
|
||||
throw new Error(`output file missing after write: ${outputPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Result builder: maps StructuralAnalysis to the expected output schema.
|
||||
// Exported for unit tests; pure function, no I/O.
|
||||
// ---------------------------------------------------------------------------
|
||||
export function buildResult(file, totalLines, nonEmptyLines, analysis, callGraph, batchImportData) {
|
||||
const base = {
|
||||
path: file.path,
|
||||
language: file.language,
|
||||
fileCategory: file.fileCategory,
|
||||
totalLines,
|
||||
nonEmptyLines,
|
||||
};
|
||||
|
||||
if (!analysis) {
|
||||
// No parser matched — return basic metrics only
|
||||
base.metrics = {};
|
||||
return base;
|
||||
}
|
||||
|
||||
// Functions (code files)
|
||||
if (analysis.functions && analysis.functions.length > 0) {
|
||||
base.functions = analysis.functions.map(fn => ({
|
||||
name: fn.name,
|
||||
startLine: fn.lineRange[0],
|
||||
endLine: fn.lineRange[1],
|
||||
params: fn.params || [],
|
||||
}));
|
||||
}
|
||||
|
||||
// Classes (code files)
|
||||
if (analysis.classes && analysis.classes.length > 0) {
|
||||
base.classes = analysis.classes.map(cls => ({
|
||||
name: cls.name,
|
||||
startLine: cls.lineRange[0],
|
||||
endLine: cls.lineRange[1],
|
||||
methods: cls.methods || [],
|
||||
properties: cls.properties || [],
|
||||
}));
|
||||
}
|
||||
|
||||
// Exports (code files)
|
||||
if (analysis.exports && analysis.exports.length > 0) {
|
||||
base.exports = analysis.exports.map(exp => ({
|
||||
name: exp.name,
|
||||
line: exp.lineNumber,
|
||||
isDefault: exp.isDefault === true,
|
||||
}));
|
||||
}
|
||||
|
||||
// Non-code structural data: pass through directly
|
||||
if (analysis.sections && analysis.sections.length > 0) {
|
||||
base.sections = analysis.sections.map(s => ({
|
||||
heading: s.name,
|
||||
level: s.level,
|
||||
line: s.lineRange[0],
|
||||
}));
|
||||
}
|
||||
|
||||
if (analysis.definitions && analysis.definitions.length > 0) {
|
||||
base.definitions = analysis.definitions.map(d => ({
|
||||
name: d.name,
|
||||
kind: d.kind,
|
||||
fields: d.fields || [],
|
||||
startLine: d.lineRange[0],
|
||||
endLine: d.lineRange[1],
|
||||
}));
|
||||
}
|
||||
|
||||
if (analysis.services && analysis.services.length > 0) {
|
||||
base.services = analysis.services.map(s => ({
|
||||
name: s.name,
|
||||
image: s.image,
|
||||
ports: s.ports || [],
|
||||
...(s.lineRange ? { startLine: s.lineRange[0], endLine: s.lineRange[1] } : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
if (analysis.endpoints && analysis.endpoints.length > 0) {
|
||||
base.endpoints = analysis.endpoints.map(e => ({
|
||||
method: e.method,
|
||||
path: e.path,
|
||||
startLine: e.lineRange[0],
|
||||
endLine: e.lineRange[1],
|
||||
}));
|
||||
}
|
||||
|
||||
if (analysis.steps && analysis.steps.length > 0) {
|
||||
base.steps = analysis.steps.map(s => ({
|
||||
name: s.name,
|
||||
startLine: s.lineRange[0],
|
||||
endLine: s.lineRange[1],
|
||||
}));
|
||||
}
|
||||
|
||||
if (analysis.resources && analysis.resources.length > 0) {
|
||||
base.resources = analysis.resources.map(r => ({
|
||||
name: r.name,
|
||||
kind: r.kind,
|
||||
startLine: r.lineRange[0],
|
||||
endLine: r.lineRange[1],
|
||||
}));
|
||||
}
|
||||
|
||||
// Call graph
|
||||
if (callGraph && callGraph.length > 0) {
|
||||
base.callGraph = callGraph;
|
||||
}
|
||||
|
||||
// Metrics
|
||||
const metrics = {};
|
||||
|
||||
// Import count from batchImportData (pre-resolved by project scanner).
|
||||
// Empty arrays are truthy, so explicitly check length so we fall back to the
|
||||
// parser's own import list when the scanner could not resolve any imports
|
||||
// (e.g. Python absolute imports the scanner doesn't follow).
|
||||
//
|
||||
// The fallback counts only relative-style imports (those starting with `.`)
|
||||
// so the metric stays *internal-import* in semantics rather than mixing in
|
||||
// every external package import seen by the parser. Resolved external imports
|
||||
// can never produce edges anyway, so counting them would be misleading.
|
||||
const importPaths = batchImportData?.[file.path];
|
||||
if (importPaths && importPaths.length > 0) {
|
||||
metrics.importCount = importPaths.length;
|
||||
} else if (analysis.imports) {
|
||||
const internal = analysis.imports.filter(imp => {
|
||||
const src = imp?.source ?? '';
|
||||
return src.startsWith('.');
|
||||
});
|
||||
metrics.importCount = internal.length;
|
||||
}
|
||||
|
||||
if (analysis.exports) {
|
||||
metrics.exportCount = analysis.exports.length;
|
||||
}
|
||||
if (analysis.functions) {
|
||||
metrics.functionCount = analysis.functions.length;
|
||||
}
|
||||
if (analysis.classes) {
|
||||
metrics.classCount = analysis.classes.length;
|
||||
}
|
||||
if (analysis.sections) {
|
||||
metrics.sectionCount = analysis.sections.length;
|
||||
}
|
||||
if (analysis.definitions) {
|
||||
metrics.definitionCount = analysis.definitions.length;
|
||||
}
|
||||
if (analysis.services) {
|
||||
metrics.serviceCount = analysis.services.length;
|
||||
}
|
||||
if (analysis.endpoints) {
|
||||
metrics.endpointCount = analysis.endpoints.length;
|
||||
}
|
||||
if (analysis.steps) {
|
||||
metrics.stepCount = analysis.steps.length;
|
||||
}
|
||||
if (analysis.resources) {
|
||||
metrics.resourceCount = analysis.resources.length;
|
||||
}
|
||||
|
||||
base.metrics = metrics;
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run only when executed directly as a CLI; importing the module (e.g. from
|
||||
// tests) must not trigger main().
|
||||
//
|
||||
// Canonicalize both sides through realpathSync. Node ESM resolves
|
||||
// import.meta.url through symlinks but pathToFileURL(process.argv[1]) preserves
|
||||
// them, so a raw equality check silently no-ops when the script is invoked via
|
||||
// a symlinked plugin install path (the default in Claude Code / Copilot CLI
|
||||
// caches). See GitHub issue #162.
|
||||
// ---------------------------------------------------------------------------
|
||||
function isCliEntry() {
|
||||
if (!process.argv[1]) return false;
|
||||
try {
|
||||
const modulePath = realpathSync(fileURLToPath(import.meta.url));
|
||||
const argvPath = realpathSync(process.argv[1]);
|
||||
return modulePath === argvPath;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCliEntry()) {
|
||||
try {
|
||||
await main();
|
||||
} catch (err) {
|
||||
process.stderr.write(`extract-structure.mjs failed: ${err.message}\n${err.stack}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
67
.qoder/skills/understand/frameworks/django.md
Normal file
67
.qoder/skills/understand/frameworks/django.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Django Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Django is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Django Project Structure
|
||||
|
||||
When analyzing a Django project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `manage.py` | CLI entry point for dev server, migrations, management commands | `entry-point`, `config` |
|
||||
| `*/settings.py`, `*/settings/*.py` | Project-wide configuration (DB, installed apps, middleware) | `config` |
|
||||
| `*/urls.py` | URL routing — maps URL patterns to views | `api-handler`, `routing` |
|
||||
| `*/views.py`, `*/views/*.py` | Request handlers (function-based or class-based views) | `api-handler`, `controller` |
|
||||
| `*/models.py`, `*/models/*.py` | ORM models — map to database tables | `data-model` |
|
||||
| `*/serializers.py` | DRF serializers — convert models to/from JSON | `serialization`, `api-handler` |
|
||||
| `*/forms.py` | Django forms — validation and rendering logic | `validation`, `ui` |
|
||||
| `*/admin.py` | Admin site registrations — exposes models in Django admin | `config` |
|
||||
| `*/signals.py` | Signal handlers — cross-cutting side effects on model events | `event-handler` |
|
||||
| `*/tasks.py` | Celery async task definitions | `service`, `event-handler` |
|
||||
| `*/middleware.py`, `*/middleware/*.py` | Request/response middleware classes | `middleware` |
|
||||
| `*/permissions.py` | DRF permission classes | `middleware`, `validation` |
|
||||
| `*/filters.py` | DRF filter backends | `utility` |
|
||||
| `*/migrations/*.py` | Auto-generated schema migrations — do not summarize individually | `config` |
|
||||
| `*/templates/**/*.html` | Django HTML templates | `ui` |
|
||||
| `*/templatetags/*.py` | Custom template filters and tags | `utility` |
|
||||
| `*/management/commands/*.py` | Custom management commands (`./manage.py mycommand`) | `config`, `entry-point` |
|
||||
| `wsgi.py`, `asgi.py` | WSGI/ASGI server adapter — production entry point | `config`, `entry-point` |
|
||||
| `*/apps.py` | App configuration and startup hooks (`AppConfig`) | `config` |
|
||||
| `*/tests.py`, `*/tests/*.py` | Unit and integration tests | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**URL routing graph** — Create `calls` edges from `urls.py` nodes to their corresponding view nodes when `path()` or `re_path()` maps a URL pattern to a view function or class. These edges represent the HTTP routing chain.
|
||||
|
||||
**Signal wiring** — When `signals.py` uses `post_save.connect(handler, sender=Model)` or `@receiver(post_save, sender=Model)`, create `subscribes` edges from the signal handler function to the model class. Create `publishes` edges from the model to the signal handler to show the trigger direction.
|
||||
|
||||
**ORM relationships** — When `models.py` defines `ForeignKey`, `OneToOneField`, or `ManyToManyField`, create `depends_on` edges between the model classes with a description indicating the relationship type and cardinality.
|
||||
|
||||
**Serializer-to-model binding** — When a DRF serializer has `model = MyModel` in its `Meta` class, create a `depends_on` edge from the serializer to the model.
|
||||
|
||||
**View-to-serializer binding** — When a DRF ViewSet or APIView references a serializer class, create a `depends_on` edge from the view to the serializer.
|
||||
|
||||
### Architectural Layers for Django
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | `views.py`, `serializers.py`, `urls.py`, DRF ViewSets and APIViews |
|
||||
| `layer:data` | Data Layer | `models.py`, `migrations/`, database utility files |
|
||||
| `layer:service` | Service Layer | `signals.py`, `tasks.py`, custom managers, service modules |
|
||||
| `layer:ui` | UI Layer | `templates/`, `forms.py`, `templatetags/` |
|
||||
| `layer:middleware` | Middleware Layer | `middleware.py`, `permissions.py`, authentication backends |
|
||||
| `layer:config` | Config Layer | `settings.py`, `urls.py` (root), `wsgi.py`, `asgi.py`, `apps.py`, `manage.py` |
|
||||
| `layer:test` | Test Layer | `tests.py`, `tests/` directory, `conftest.py` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Fat models vs. thin views**: Django encourages business logic in model methods, keeping views thin HTTP adapters
|
||||
- **Django ORM lazy evaluation**: QuerySets are not evaluated until iterated — chain filters without DB hits
|
||||
- **Class-based views (CBVs)**: Mixins like `LoginRequiredMixin`, `PermissionRequiredMixin` compose behavior through multiple inheritance
|
||||
- **Signal anti-patterns**: Signals create invisible coupling; a signal in `signals.py` may be triggered by a `save()` call anywhere in the codebase
|
||||
- **App isolation**: Each Django app (`INSTALLED_APPS`) should be self-contained with its own models, views, urls, and migrations
|
||||
57
.qoder/skills/understand/frameworks/express.md
Normal file
57
.qoder/skills/understand/frameworks/express.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Express Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Express is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Express Project Structure
|
||||
|
||||
When analyzing an Express project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `app.js`, `app.ts` | Application entry point — creates Express app, mounts middleware and routes | `entry-point`, `config` |
|
||||
| `server.js`, `server.ts`, `index.js`, `index.ts` | Server bootstrap — starts HTTP listener, may import app | `entry-point`, `config` |
|
||||
| `routes/*.js`, `routes/*.ts` | Route definitions — map HTTP methods and paths to handlers | `api-handler`, `routing` |
|
||||
| `controllers/*.js`, `controllers/*.ts` | Request handlers — process requests, orchestrate services, return responses | `api-handler`, `service` |
|
||||
| `models/*.js`, `models/*.ts` | Data models — Mongoose schemas, Sequelize models, or plain data definitions | `data-model` |
|
||||
| `middleware/*.js`, `middleware/*.ts` | Middleware functions — authentication, logging, validation, error handling | `middleware` |
|
||||
| `services/*.js`, `services/*.ts` | Business logic — domain operations decoupled from HTTP layer | `service` |
|
||||
| `db/*.js`, `db/*.ts`, `database/*.js` | Database connection and configuration | `data-model`, `config` |
|
||||
| `config/*.js`, `config/*.ts` | Application configuration — environment variables, feature flags | `config` |
|
||||
| `validators/*.js`, `validators/*.ts` | Request validation schemas (Joi, Zod, express-validator) | `validation`, `utility` |
|
||||
| `utils/*.js`, `utils/*.ts` | Shared utility functions | `utility` |
|
||||
| `tests/*.js`, `test/*.js`, `__tests__/*.js` | Unit and integration tests | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Route mounting** — When `app.use('/api/users', usersRouter)` mounts a router, create `depends_on` edges from the main app to the router module. These edges represent the HTTP routing tree.
|
||||
|
||||
**Middleware chain** — When `app.use(cors())`, `app.use(authMiddleware)`, or `router.use(validate)` registers middleware, create middleware edges from the app or router to the middleware function. Order matters — middleware executes in registration order.
|
||||
|
||||
**Controller-to-service calls** — When a controller imports and calls a service function, create `depends_on` edges from the controller to the service. This represents the separation between HTTP handling and business logic.
|
||||
|
||||
**Model relationships** — When models reference each other (Mongoose `ref`, Sequelize associations), create `depends_on` edges between model files with descriptions indicating the relationship type.
|
||||
|
||||
### Architectural Layers for Express
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | `routes/`, `controllers/`, request validators |
|
||||
| `layer:data` | Data Layer | `models/`, `db/`, migration files, seeders |
|
||||
| `layer:service` | Service Layer | `services/`, business logic modules |
|
||||
| `layer:middleware` | Middleware Layer | `middleware/`, error handlers, authentication, logging |
|
||||
| `layer:config` | Config Layer | `app.js`, `config/`, environment setup, `server.js` |
|
||||
| `layer:utility` | Utility Layer | `utils/`, `helpers/`, shared pure functions |
|
||||
| `layer:test` | Test Layer | `tests/`, `__tests__/`, `*.test.js`, `*.spec.js` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Middleware chain (req, res, next)**: Express processes requests through a pipeline of middleware functions — each receives the request, response, and a `next()` callback to pass control forward
|
||||
- **Error-handling middleware (4 params)**: Middleware with signature `(err, req, res, next)` catches errors — must be registered after all routes to act as a global error handler
|
||||
- **Router modularity**: `express.Router()` creates modular, mountable route handlers that can be composed into the main app at different path prefixes
|
||||
- **MVC pattern**: Express apps commonly separate concerns into Models (data), Views (response formatting), and Controllers (request handling)
|
||||
- **Body parsing and validation**: Request body parsing (`express.json()`, `express.urlencoded()`) and validation (Joi, Zod, express-validator) are middleware concerns applied before route handlers
|
||||
58
.qoder/skills/understand/frameworks/fastapi.md
Normal file
58
.qoder/skills/understand/frameworks/fastapi.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# FastAPI Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when FastAPI is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## FastAPI Project Structure
|
||||
|
||||
When analyzing a FastAPI project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `main.py`, `app.py` | Application factory — creates and configures the `FastAPI()` instance | `entry-point`, `config` |
|
||||
| `*/routers/*.py`, `*/api/*.py` | `APIRouter` modules — group related endpoints by domain | `api-handler`, `routing` |
|
||||
| `*/schemas.py`, `*/schemas/*.py` | Pydantic request/response models | `type-definition`, `serialization` |
|
||||
| `*/models.py`, `*/models/*.py` | SQLAlchemy ORM models or other DB models | `data-model` |
|
||||
| `*/dependencies.py`, `*/deps.py` | `Depends()` provider functions — shared logic injected into routes | `service`, `middleware` |
|
||||
| `*/crud.py`, `*/repository.py` | Database access layer — CRUD operations | `data-model`, `service` |
|
||||
| `*/database.py`, `*/db.py` | DB engine, session factory, connection management | `config`, `data-model` |
|
||||
| `*/config.py`, `*/settings.py` | `pydantic-settings` / `BaseSettings` config classes | `config` |
|
||||
| `*/middleware.py` | Starlette middleware classes | `middleware` |
|
||||
| `*/exceptions.py` | Custom exception classes and exception handlers | `utility` |
|
||||
| `*/security.py`, `*/auth.py` | Auth utilities — JWT decoding, password hashing, OAuth helpers | `service`, `middleware` |
|
||||
| `*/tasks.py` | Background tasks or Celery task definitions | `service`, `event-handler` |
|
||||
| `*/tests/*.py`, `test_*.py` | pytest test files | `test` |
|
||||
| `conftest.py` | pytest fixtures and test configuration | `test`, `config` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Router inclusion chain** — When `app.include_router(some_router, prefix="/api")` appears in `main.py` or a router aggregator, create `imports` + `depends_on` edges from the main app file to each router module. This builds the URL hierarchy graph.
|
||||
|
||||
**Dependency injection tree** — When a route function or another `Depends()` provider imports and calls `Depends(some_function)`, create `depends_on` edges from the caller to the dependency provider. Trace these chains — they often span multiple files (e.g., route → auth dependency → DB session dependency).
|
||||
|
||||
**Pydantic model inheritance** — When a schema class inherits from another (e.g., `class UserCreate(UserBase)`), create `inherits` edges between the schema class nodes.
|
||||
|
||||
**ORM model relationships** — When SQLAlchemy models use `relationship()`, `ForeignKey`, create `depends_on` edges between the model classes.
|
||||
|
||||
**CRUD-to-model binding** — When a `crud.py` function takes a model type as an argument or directly references a model class, create `depends_on` edges from the CRUD file to the model file.
|
||||
|
||||
### Architectural Layers for FastAPI
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | Router files, endpoint functions with `@router.get/post/...` decorators |
|
||||
| `layer:types` | Types Layer | Pydantic schema files, request/response models |
|
||||
| `layer:service` | Service Layer | `dependencies.py`, `crud.py`, business logic modules |
|
||||
| `layer:data` | Data Layer | ORM models, `database.py`, migrations |
|
||||
| `layer:config` | Config Layer | `main.py` / `app.py` factory, `settings.py`, `config.py` |
|
||||
| `layer:middleware` | Middleware Layer | `middleware.py`, `security.py`, `auth.py`, exception handlers |
|
||||
| `layer:test` | Test Layer | `tests/`, `conftest.py` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Dependency injection as composition**: FastAPI's `Depends()` is a first-class DI system — a route can declare any number of dependencies, each of which can have their own dependencies, forming a tree resolved at request time
|
||||
- **Pydantic for validation**: Request bodies, query params, and path params are automatically validated by Pydantic — invalid input raises `422 Unprocessable Entity` before your code runs
|
||||
- **Async endpoints**: `async def` routes run in the event loop; `def` routes run in a threadpool — mixing them incorrectly can cause performance issues
|
||||
- **Path operation order**: FastAPI matches routes in declaration order; a catch-all route before a specific one will shadow it
|
||||
53
.qoder/skills/understand/frameworks/flask.md
Normal file
53
.qoder/skills/understand/frameworks/flask.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Flask Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Flask is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Flask Project Structure
|
||||
|
||||
When analyzing a Flask project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `app.py`, `__init__.py` (in app package) | Application factory (`create_app()`) or direct `Flask(__name__)` instance | `entry-point`, `config` |
|
||||
| `run.py`, `wsgi.py` | Production/dev server entry point | `entry-point`, `config` |
|
||||
| `*/views.py`, `*/routes.py` | Route handler functions with `@app.route` or `@blueprint.route` | `api-handler`, `routing` |
|
||||
| `*/blueprints/*.py`, `*/api/*.py` | Blueprint modules — group routes by feature | `api-handler`, `routing` |
|
||||
| `*/models.py` | SQLAlchemy models or other ORM models | `data-model` |
|
||||
| `*/forms.py` | WTForms form classes | `validation`, `ui` |
|
||||
| `*/schemas.py` | Marshmallow serialization schemas | `serialization`, `type-definition` |
|
||||
| `*/config.py` | Config classes (`DevelopmentConfig`, `ProductionConfig`) | `config` |
|
||||
| `*/extensions.py` | Flask extension initialization (`db = SQLAlchemy()`, `login_manager = LoginManager()`) | `config`, `singleton` |
|
||||
| `*/decorators.py` | Custom route decorators (auth guards, rate limiting) | `middleware`, `utility` |
|
||||
| `*/utils.py`, `*/helpers.py` | Shared utility functions | `utility` |
|
||||
| `*/templates/**/*.html` | Jinja2 templates | `ui` |
|
||||
| `*/static/` | CSS, JS, and asset files | `assets` |
|
||||
| `*/tests/*.py`, `test_*.py` | pytest or unittest test files | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Blueprint registration** — When `app.register_blueprint(bp, url_prefix='/api')` appears in the application factory, create `depends_on` edges from the app factory to each blueprint module.
|
||||
|
||||
**Extension coupling** — When a view imports from `extensions.py` (e.g., `from .extensions import db, login_manager`), create `imports` edges to show which views depend on which extensions.
|
||||
|
||||
**Before/after request hooks** — When `@app.before_request` or `@blueprint.before_request` decorates a function, create `middleware` edges from those functions to the app/blueprint they attach to.
|
||||
|
||||
### Architectural Layers for Flask
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | Blueprint route files, view functions |
|
||||
| `layer:data` | Data Layer | `models.py`, database migration files |
|
||||
| `layer:service` | Service Layer | Business logic modules, `schemas.py`, service classes |
|
||||
| `layer:ui` | UI Layer | `templates/`, `forms.py`, `static/` |
|
||||
| `layer:config` | Config Layer | `app.py` factory, `config.py`, `extensions.py` |
|
||||
| `layer:middleware` | Middleware Layer | `decorators.py`, before/after request hooks |
|
||||
| `layer:test` | Test Layer | Test files, `conftest.py` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Application factory pattern**: `create_app()` functions allow multiple app instances (e.g., for testing) and delay extension initialization — avoids circular imports
|
||||
- **Blueprint modularity**: Blueprints group related routes, templates, and static files; they are registered on the app with a URL prefix, making them independently testable
|
||||
- **Flask extension protocol**: Extensions follow `init_app(app)` for lazy initialization — the extension object is created globally but bound to an app instance later
|
||||
59
.qoder/skills/understand/frameworks/gin.md
Normal file
59
.qoder/skills/understand/frameworks/gin.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Gin (Go) Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Gin is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Gin Project Structure
|
||||
|
||||
When analyzing a Gin project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `main.go` | Application entry point — initializes the Gin engine, registers routes, starts the server | `entry-point`, `config` |
|
||||
| `cmd/*.go`, `cmd/**/*.go` | CLI entry points — multiple binaries in a multi-command project | `entry-point`, `config` |
|
||||
| `handlers/*.go`, `handler/*.go` | HTTP handlers — process requests with `gin.Context` | `api-handler` |
|
||||
| `controllers/*.go`, `controller/*.go` | Controllers — alternative naming for HTTP handlers | `api-handler` |
|
||||
| `routes/*.go`, `router/*.go` | Route definitions — register endpoints and route groups | `routing`, `config` |
|
||||
| `models/*.go`, `model/*.go` | Data models — struct definitions mapped to database tables | `data-model` |
|
||||
| `middleware/*.go` | Middleware functions — authentication, logging, CORS, rate limiting | `middleware` |
|
||||
| `services/*.go`, `service/*.go` | Business logic — domain operations decoupled from HTTP layer | `service` |
|
||||
| `repository/*.go`, `repo/*.go` | Data access layer — database queries and persistence logic | `data-model`, `service` |
|
||||
| `config/*.go`, `config.go` | Application configuration — environment loading, struct-based config | `config` |
|
||||
| `dto/*.go` | Data transfer objects — request and response structs | `type-definition` |
|
||||
| `utils/*.go`, `pkg/*.go` | Shared utility packages | `utility` |
|
||||
| `*_test.go` | Unit and integration tests | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Route group registration** — When `r.Group("/api")` creates a route group and registers handlers, create `configures` edges from the route definition file to each handler. Route groups organize endpoints by prefix and shared middleware.
|
||||
|
||||
**Handler-to-service calls** — When a handler function calls a service method, create `depends_on` edges from the handler to the service. This represents the separation between HTTP handling and business logic.
|
||||
|
||||
**Service-to-repository calls** — When a service calls a repository method for data access, create `depends_on` edges from the service to the repository. This represents the data access abstraction.
|
||||
|
||||
**Middleware chaining** — When `r.Use(middleware)` or a route group applies middleware, create middleware edges from the router or group to the middleware function. Middleware executes in registration order.
|
||||
|
||||
### Architectural Layers for Gin
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | `handlers/`, `controllers/`, HTTP handler functions |
|
||||
| `layer:data` | Data Layer | `models/`, `repository/`, database access, migrations |
|
||||
| `layer:service` | Service Layer | `services/`, business logic |
|
||||
| `layer:middleware` | Middleware Layer | `middleware/`, authentication, logging, rate limiting |
|
||||
| `layer:config` | Config Layer | `main.go`, `routes/`, `config/`, environment setup |
|
||||
| `layer:utility` | Utility Layer | `utils/`, `pkg/`, shared helper packages |
|
||||
| `layer:test` | Test Layer | `*_test.go`, test fixtures, test helpers |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Handler functions with gin.Context**: Every Gin handler receives a `*gin.Context` parameter — it provides request parsing (`c.Bind`, `c.Param`, `c.Query`), response writing (`c.JSON`, `c.HTML`), and control flow (`c.Abort`, `c.Next`)
|
||||
- **Middleware chain with c.Next()**: Middleware calls `c.Next()` to pass control to the next handler in the chain — code before `c.Next()` runs pre-handler, code after runs post-handler
|
||||
- **Route grouping for modular APIs**: `r.Group("/v1")` creates modular sub-routers that can have their own middleware stack — enables versioning and access control at the group level
|
||||
- **Dependency injection via constructors (no framework DI)**: Go has no DI framework — dependencies are passed as constructor parameters (e.g., `NewUserHandler(userService)`) and stored as struct fields
|
||||
- **Interface-driven design for testability**: Services and repositories are defined as interfaces — handlers depend on the interface, enabling mock implementations in tests
|
||||
- **Error handling with gin.Error**: Gin collects errors via `c.Error(err)` — middleware can inspect `c.Errors` after handler execution to implement centralized error logging and response formatting
|
||||
59
.qoder/skills/understand/frameworks/nextjs.md
Normal file
59
.qoder/skills/understand/frameworks/nextjs.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Next.js Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Next.js is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Next.js Project Structure
|
||||
|
||||
When analyzing a Next.js project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `app/layout.tsx` | Root layout — wraps all pages, defines HTML shell and global providers | `entry-point`, `config`, `ui` |
|
||||
| `app/page.tsx` | Root page component — renders at `/` | `ui`, `routing` |
|
||||
| `app/**/page.tsx` | Route page components — file path determines URL | `ui`, `routing` |
|
||||
| `app/**/layout.tsx` | Nested layouts — wrap child routes with shared UI | `ui`, `config` |
|
||||
| `app/**/loading.tsx` | Loading UI — shown as Suspense fallback during route transitions | `ui` |
|
||||
| `app/**/error.tsx` | Error boundary — catches errors in the route segment | `ui` |
|
||||
| `app/**/not-found.tsx` | 404 UI — shown when `notFound()` is called | `ui` |
|
||||
| `app/api/**/route.ts` | API route handlers — serverless endpoint functions (GET, POST, etc.) | `api-handler` |
|
||||
| `middleware.ts` | Edge middleware — intercepts requests before they reach routes | `middleware` |
|
||||
| `lib/*.ts`, `lib/**/*.ts` | Shared server-side utilities, data access, and business logic | `service` |
|
||||
| `components/*.tsx`, `components/**/*.tsx` | Reusable UI components | `ui` |
|
||||
| `next.config.js`, `next.config.mjs`, `next.config.ts` | Next.js configuration — redirects, rewrites, env, webpack overrides | `config` |
|
||||
| `actions/*.ts`, `app/**/actions.ts` | Server Actions — server-side mutation functions callable from client | `service`, `api-handler` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Layout nesting** — When `app/foo/layout.tsx` wraps `app/foo/page.tsx` and `app/foo/bar/page.tsx`, create `contains` edges from the layout to the pages it wraps. Layouts compose via the file-system hierarchy.
|
||||
|
||||
**API route handlers** — When a `route.ts` file exports named functions (GET, POST, PUT, DELETE), create edges from consuming components or server actions to the route handler based on fetch calls.
|
||||
|
||||
**Server/Client component boundary** — Files with `"use client"` directive at the top are Client Components. All other components in the `app/` directory are Server Components by default. Create `depends_on` edges that cross this boundary and note the boundary in the edge description.
|
||||
|
||||
**Parallel routes** — When `app/@slot/page.tsx` patterns appear, create `contains` edges from the parent layout to each parallel slot. These render simultaneously in the same layout.
|
||||
|
||||
**Route groups** — Directories wrapped in parentheses `(group)` organize routes without affecting the URL path. Note these in node descriptions.
|
||||
|
||||
### Architectural Layers for Next.js
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:ui` | UI Layer | `app/**/page.tsx`, `app/**/layout.tsx`, `components/`, loading/error boundaries |
|
||||
| `layer:api` | API Layer | `app/api/**/route.ts`, API route handlers |
|
||||
| `layer:service` | Service Layer | `lib/`, server actions, data-fetching utilities |
|
||||
| `layer:middleware` | Middleware Layer | `middleware.ts`, edge functions |
|
||||
| `layer:config` | Config Layer | `next.config.*`, root layout, `tailwind.config.*`, environment setup |
|
||||
| `layer:test` | Test Layer | `__tests__/`, `*.test.tsx`, `*.spec.tsx`, `e2e/` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Server Components by default**: Components in the `app/` directory are Server Components — no JavaScript is sent to the client unless `"use client"` is declared
|
||||
- **Server Actions for mutations**: Functions marked with `"use server"` can be called directly from client components, replacing traditional API routes for form submissions and mutations
|
||||
- **App Router file conventions**: Special files (`page`, `layout`, `loading`, `error`, `not-found`, `route`) define behavior by naming convention within the file-system router
|
||||
- **ISR and static generation**: `generateStaticParams` pre-renders pages at build time; revalidation strategies control cache freshness
|
||||
- **Parallel and intercepting routes**: `@slot` directories enable parallel rendering; `(.)` prefix directories enable route interception for modal patterns
|
||||
65
.qoder/skills/understand/frameworks/rails.md
Normal file
65
.qoder/skills/understand/frameworks/rails.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Ruby on Rails Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Rails is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Rails Project Structure
|
||||
|
||||
When analyzing a Ruby on Rails project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `config.ru` | Rack entry point — boots the Rails application for the web server | `entry-point` |
|
||||
| `config/application.rb` | Application configuration — sets up Rails, loads gems, configures middleware | `entry-point`, `config` |
|
||||
| `app/controllers/*_controller.rb` | Controllers — handle HTTP requests, orchestrate models, render responses | `api-handler` |
|
||||
| `app/controllers/concerns/*.rb` | Controller concerns — shared controller behavior via mixins | `middleware`, `utility` |
|
||||
| `app/models/*.rb` | ActiveRecord models — map to database tables, contain validations and associations | `data-model` |
|
||||
| `app/models/concerns/*.rb` | Model concerns — shared model behavior via mixins | `utility` |
|
||||
| `app/views/**/*.erb`, `app/views/**/*.haml` | View templates — HTML rendering with embedded Ruby | `ui` |
|
||||
| `app/helpers/*_helper.rb` | View helpers — utility methods available in templates | `utility` |
|
||||
| `app/mailers/*_mailer.rb` | Action Mailer classes — send email notifications | `service` |
|
||||
| `app/jobs/*_job.rb` | Active Job classes — background job processing | `service` |
|
||||
| `app/channels/*_channel.rb` | Action Cable channels — WebSocket communication | `service` |
|
||||
| `app/serializers/*_serializer.rb` | API serializers — JSON response formatting (ActiveModelSerializers, Blueprinter) | `api-handler`, `utility` |
|
||||
| `app/services/*.rb` | Service objects — encapsulate complex business logic | `service` |
|
||||
| `db/migrate/*.rb` | Database migrations — schema changes versioned by timestamp | `config`, `data-model` |
|
||||
| `db/schema.rb`, `db/structure.sql` | Generated schema snapshot — current database structure | `data-model`, `config` |
|
||||
| `config/routes.rb` | Route definitions — maps URLs to controller actions | `routing`, `config` |
|
||||
| `config/initializers/*.rb` | Initializers — run once at boot to configure gems and services | `config` |
|
||||
| `lib/**/*.rb` | Library code — custom classes, Rake tasks, extensions | `utility`, `service` |
|
||||
| `spec/**/*_spec.rb`, `test/**/*_test.rb` | RSpec or Minitest test files | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Route-to-controller mapping** — When `config/routes.rb` defines `resources :users` or `get '/foo', to: 'bar#baz'`, create `configures` edges from the routes file to the corresponding controller. RESTful resources generate a full set of action mappings.
|
||||
|
||||
**ActiveRecord associations** — When models define `has_many`, `belongs_to`, `has_one`, or `has_and_belongs_to_many`, create `depends_on` edges between model files with descriptions indicating the association type and direction.
|
||||
|
||||
**Controller-to-model** — When a controller calls model methods (`User.find`, `@post.save`), create `depends_on` edges from the controller to the model. Controllers are the primary consumers of model data.
|
||||
|
||||
**Callbacks** — When models or controllers use `before_action`, `after_save`, `before_validation`, or similar callbacks, note these as middleware-like edges. Callbacks create implicit execution paths that are not visible from the call site.
|
||||
|
||||
### Architectural Layers for Rails
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | `app/controllers/`, `app/serializers/`, API-specific controllers |
|
||||
| `layer:data` | Data Layer | `app/models/`, `db/migrate/`, `db/schema.rb` |
|
||||
| `layer:ui` | UI Layer | `app/views/`, `app/helpers/`, `app/assets/`, `app/javascript/` |
|
||||
| `layer:service` | Service Layer | `app/mailers/`, `app/jobs/`, `app/channels/`, `app/services/`, `lib/` |
|
||||
| `layer:config` | Config Layer | `config/routes.rb`, `config/initializers/`, `config/application.rb`, `config.ru` |
|
||||
| `layer:middleware` | Middleware Layer | `app/middleware/`, controller concerns, Rack middleware |
|
||||
| `layer:test` | Test Layer | `spec/`, `test/`, `*.spec.rb`, `*_test.rb` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Convention over configuration**: Rails derives routing, table names, and file locations from naming conventions — `UsersController` maps to `users_controller.rb`, handles `/users`, and queries the `users` table
|
||||
- **ActiveRecord pattern**: Models are database wrappers — each model class maps to a table, instances map to rows, and attributes map to columns with automatic type coercion
|
||||
- **Concerns for shared behavior**: `ActiveSupport::Concern` modules are mixins included in models or controllers to share validations, scopes, callbacks, and methods across classes
|
||||
- **Strong parameters for mass-assignment protection**: `params.require(:user).permit(:name, :email)` whitelists attributes — controllers must explicitly declare which fields can be set from user input
|
||||
- **RESTful resource routing**: `resources :posts` generates seven standard CRUD routes — Rails strongly encourages RESTful design where each controller maps to a resource
|
||||
- **Callbacks and observers**: `before_save`, `after_create`, and similar callbacks inject logic into the object lifecycle — they create invisible execution paths that can be difficult to trace
|
||||
55
.qoder/skills/understand/frameworks/react.md
Normal file
55
.qoder/skills/understand/frameworks/react.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# React Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when React is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## React Project Structure
|
||||
|
||||
When analyzing a React project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `src/App.tsx` | Root application component — mounts providers, router, and top-level layout | `entry-point`, `ui` |
|
||||
| `components/*.tsx`, `components/**/*.tsx` | Reusable UI components | `ui` |
|
||||
| `hooks/*.ts`, `hooks/*.tsx` | Custom React hooks — encapsulate reusable stateful logic | `service`, `utility` |
|
||||
| `contexts/*.tsx`, `context/*.tsx` | React Context providers and consumers — shared state across component tree | `service`, `state` |
|
||||
| `pages/*.tsx`, `views/*.tsx` | Page-level components mapped to routes | `ui`, `routing` |
|
||||
| `utils/*.ts`, `helpers/*.ts` | Pure utility functions — formatting, validation, transformations | `utility` |
|
||||
| `types/*.ts`, `types/*.d.ts` | TypeScript type definitions and interfaces | `type-definition` |
|
||||
| `services/*.ts`, `api/*.ts` | API client functions and data-fetching logic | `service` |
|
||||
| `store/*.ts`, `slices/*.ts` | State management (Redux, Zustand, etc.) | `service`, `state` |
|
||||
| `constants/*.ts` | Application-wide constants and enums | `config` |
|
||||
| `__tests__/*.tsx`, `*.test.tsx`, `*.spec.tsx` | Unit and integration tests | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Component composition** — When a parent component renders a child component in its JSX return, create `contains` edges from the parent to the child. These edges represent the component tree hierarchy.
|
||||
|
||||
**Hook usage** — When a component or hook imports and calls a custom hook (`useX`), create `depends_on` edges from the consumer to the hook module. Hooks are the primary mechanism for shared logic in React.
|
||||
|
||||
**Context provider/consumer** — When a Context provider wraps components, create `publishes` edges from the provider to the context definition. When components call `useContext` or use a custom context hook, create `subscribes` edges from the consumer to the context.
|
||||
|
||||
**Props drilling chains** — When props are passed through multiple component layers without being used, create `depends_on` edges along the chain to surface the coupling depth.
|
||||
|
||||
### Architectural Layers for React
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:ui` | UI Layer | `components/`, `pages/`, `views/`, layout components |
|
||||
| `layer:service` | Service Layer | `hooks/`, `contexts/`, `services/`, `api/`, `store/` |
|
||||
| `layer:types` | Types Layer | `types/`, shared TypeScript interfaces and type definitions |
|
||||
| `layer:utility` | Utility Layer | `utils/`, `helpers/`, pure functions |
|
||||
| `layer:config` | Config Layer | `App.tsx`, router configuration, provider setup, constants |
|
||||
| `layer:test` | Test Layer | `__tests__/`, `*.test.tsx`, `*.spec.tsx` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Component composition over inheritance**: React favors composing components via props and children rather than class inheritance hierarchies
|
||||
- **Custom hooks for reusable logic**: Hooks prefixed with `use` extract stateful logic into shareable modules without changing the component tree
|
||||
- **React.memo for performance**: Components wrapped in `React.memo` skip re-renders when props are unchanged — indicates performance-sensitive paths
|
||||
- **Controlled vs. uncontrolled components**: Controlled components derive state from props; uncontrolled components manage internal state via refs
|
||||
- **Render props pattern**: Components that accept a function as children or a render prop to delegate rendering decisions to the consumer
|
||||
59
.qoder/skills/understand/frameworks/spring.md
Normal file
59
.qoder/skills/understand/frameworks/spring.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Spring Boot Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Spring Boot is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Spring Boot Project Structure
|
||||
|
||||
When analyzing a Spring Boot project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `*Application.java`, `*Application.kt` | Application entry point — `@SpringBootApplication` class with `main()` method | `entry-point`, `config` |
|
||||
| `*Controller.java`, `*RestController.java` | REST controllers — handle HTTP requests, delegate to services | `api-handler` |
|
||||
| `*Service.java` | Service interfaces — define business operation contracts | `service` |
|
||||
| `*ServiceImpl.java` | Service implementations — contain business logic | `service` |
|
||||
| `*Repository.java` | Spring Data repositories — data access interfaces extending JpaRepository/CrudRepository | `data-model` |
|
||||
| `*Entity.java` | JPA entities — map to database tables via `@Entity` annotation | `data-model` |
|
||||
| `*DTO.java`, `*Request.java`, `*Response.java` | Data transfer objects — request/response payloads | `type-definition` |
|
||||
| `*Config.java`, `*Configuration.java` | Configuration classes — `@Configuration` beans, security config, web config | `config` |
|
||||
| `*Filter.java` | Servlet filters — intercept requests before they reach controllers | `middleware` |
|
||||
| `*Interceptor.java` | Handler interceptors — pre/post processing around controller methods | `middleware` |
|
||||
| `*Advice.java`, `*ExceptionHandler.java` | Controller advice — global exception handling and response wrapping | `middleware` |
|
||||
| `*Mapper.java` | Object mappers — convert between entities and DTOs (MapStruct, ModelMapper) | `utility` |
|
||||
| `application.yml`, `application.properties` | Application configuration — profiles, datasource, server settings | `config` |
|
||||
| `*Test.java`, `*Tests.java`, `*IT.java` | Unit tests, integration tests | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**@Autowired injection** — When a class injects a dependency via `@Autowired`, constructor injection, or `@Inject`, create `depends_on` edges from the consumer to the injected bean. Constructor injection is preferred and most common in modern Spring.
|
||||
|
||||
**Controller-Service-Repository chain** — The canonical call chain is `@RestController` -> `@Service` -> `@Repository`. Create `depends_on` edges along this chain to show the layered architecture.
|
||||
|
||||
**@Entity relationships** — When entities define `@OneToMany`, `@ManyToOne`, `@OneToOne`, or `@ManyToMany` annotations, create `depends_on` edges between entity classes with descriptions indicating the relationship type and direction.
|
||||
|
||||
**@Configuration bean definitions** — When a `@Configuration` class defines `@Bean` methods, create `configures` edges from the configuration class to the types it produces. These beans become available for injection throughout the application.
|
||||
|
||||
### Architectural Layers for Spring Boot
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:api` | API Layer | `*Controller.java`, REST endpoints, API documentation |
|
||||
| `layer:service` | Service Layer | `*Service.java`, `*ServiceImpl.java`, business logic |
|
||||
| `layer:data` | Data Layer | `*Repository.java`, `*Entity.java`, JPA mappings, database migrations |
|
||||
| `layer:types` | Types Layer | `*DTO.java`, `*Request.java`, `*Response.java`, shared value objects |
|
||||
| `layer:config` | Config Layer | `*Configuration.java`, `application.yml`, security config, `*Application.java` |
|
||||
| `layer:middleware` | Middleware Layer | `*Filter.java`, `*Interceptor.java`, `*Advice.java`, security filters |
|
||||
| `layer:test` | Test Layer | `*Test.java`, `*Tests.java`, `*IT.java`, test configuration |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Dependency injection via constructor injection**: Spring favors constructor injection over field injection (`@Autowired` on fields) — it makes dependencies explicit, supports immutability, and simplifies testing
|
||||
- **Layered architecture (Controller -> Service -> Repository)**: Spring Boot applications follow a strict layered pattern where controllers handle HTTP, services contain business logic, and repositories manage persistence
|
||||
- **Spring Security filter chain**: Security is implemented as a chain of servlet filters — `SecurityFilterChain` beans configure authentication, authorization, CORS, and CSRF protection
|
||||
- **JPA entity lifecycle**: Entities transition through states (transient, managed, detached, removed) — understanding this lifecycle is essential for tracing data flow through the persistence layer
|
||||
- **AOP for cross-cutting concerns**: `@Aspect` classes with `@Before`, `@After`, and `@Around` advice inject behavior at join points — used for logging, transactions (`@Transactional`), and caching (`@Cacheable`)
|
||||
59
.qoder/skills/understand/frameworks/vue.md
Normal file
59
.qoder/skills/understand/frameworks/vue.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Vue Framework Addendum
|
||||
|
||||
> Injected into file-analyzer and architecture-analyzer prompts when Vue is detected.
|
||||
> Do NOT use as a standalone prompt — always appended to the base prompt template.
|
||||
|
||||
## Vue Project Structure
|
||||
|
||||
When analyzing a Vue project, apply these additional conventions on top of the base analysis rules.
|
||||
|
||||
### Canonical File Roles
|
||||
|
||||
| File / Pattern | Role | Tags |
|
||||
|---|---|---|
|
||||
| `src/App.vue` | Root application component — mounts the top-level layout and router view | `entry-point`, `ui` |
|
||||
| `src/main.ts`, `src/main.js` | Application bootstrap — creates Vue app instance, registers plugins, mounts to DOM | `entry-point`, `config` |
|
||||
| `components/*.vue`, `components/**/*.vue` | Reusable UI components | `ui` |
|
||||
| `views/*.vue`, `pages/*.vue` | Page-level components mapped to routes | `ui`, `routing` |
|
||||
| `composables/*.ts`, `composables/*.js` | Composable functions — reusable stateful logic using Composition API | `service`, `utility` |
|
||||
| `store/*.ts`, `stores/*.ts` | State management modules (Pinia stores or Vuex modules) | `service`, `state` |
|
||||
| `router/*.ts`, `router/index.ts` | Vue Router configuration — route definitions, navigation guards | `config`, `routing` |
|
||||
| `plugins/*.ts`, `plugins/*.js` | Vue plugin registrations — extend app functionality (i18n, auth, etc.) | `config` |
|
||||
| `utils/*.ts`, `helpers/*.ts` | Pure utility functions | `utility` |
|
||||
| `types/*.ts`, `types/*.d.ts` | TypeScript type definitions and interfaces | `type-definition` |
|
||||
| `api/*.ts`, `services/*.ts` | API client functions and data-fetching logic | `service` |
|
||||
| `directives/*.ts` | Custom Vue directives | `utility` |
|
||||
| `tests/*.spec.ts`, `__tests__/*.spec.ts` | Unit and integration tests | `test` |
|
||||
|
||||
### Edge Patterns to Look For
|
||||
|
||||
**Component parent-child** — When a parent component uses a child component in its `<template>`, create `contains` edges from the parent to the child. Template refs and slot usage further indicate composition relationships.
|
||||
|
||||
**Composable usage** — When a component or composable imports and calls a `useX` function, create `depends_on` edges from the consumer to the composable module. Composables are the primary mechanism for shared stateful logic.
|
||||
|
||||
**Store actions/getters** — When components or composables import and use a Pinia store (`useXStore()`), create `depends_on` edges from the consumer to the store. Store-to-store dependencies should also be captured.
|
||||
|
||||
**Router view mapping** — When `router/index.ts` maps paths to view components, create `configures` edges from the router to each view component. Navigation guards add middleware-like edges.
|
||||
|
||||
**Plugin registration** — When `main.ts` calls `app.use(plugin)`, create `configures` edges from the bootstrap file to each plugin.
|
||||
|
||||
### Architectural Layers for Vue
|
||||
|
||||
Assign nodes to these layers when detected:
|
||||
|
||||
| Layer ID | Layer Name | What Goes Here |
|
||||
|---|---|---|
|
||||
| `layer:ui` | UI Layer | `components/`, `views/`, `pages/`, layout components |
|
||||
| `layer:service` | Service Layer | `composables/`, `store/`, `stores/`, `api/`, `services/` |
|
||||
| `layer:config` | Config Layer | `router/`, `plugins/`, `main.ts`, `App.vue`, configuration files |
|
||||
| `layer:utility` | Utility Layer | `utils/`, `helpers/`, `directives/`, pure functions |
|
||||
| `layer:test` | Test Layer | `tests/`, `__tests__/`, `*.spec.ts` |
|
||||
|
||||
### Notable Patterns to Capture in languageLesson
|
||||
|
||||
- **Composition API over Options API**: Modern Vue favors `setup()` and `<script setup>` with composables, replacing the Options API's data/methods/computed separation
|
||||
- **Pinia for state management**: Pinia stores provide type-safe, modular state with actions and getters — each store is independently defined and can depend on other stores
|
||||
- **Vue Router with navigation guards**: `beforeEach`, `beforeEnter`, and `afterEach` guards act as middleware for route transitions — used for authentication and data prefetching
|
||||
- **Single-file components (.vue)**: Each `.vue` file encapsulates template, script, and style in a single file — the `<script setup>` syntax is the recommended concise form
|
||||
- **Reactive refs and computed properties**: `ref()` and `reactive()` create reactive state; `computed()` derives values that auto-update — understanding reactivity is key to tracing data flow
|
||||
- **Provide/inject for deep dependency passing**: `provide()` and `inject()` pass values down the component tree without prop drilling — creates implicit dependencies that should be captured as edges
|
||||
47
.qoder/skills/understand/languages/cpp.md
Normal file
47
.qoder/skills/understand/languages/cpp.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# C++ Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Templates**: Function, class, and variadic templates for generic compile-time polymorphism
|
||||
- **RAII**: Resource Acquisition Is Initialization — tie resource lifetime to object scope
|
||||
- **Smart Pointers**: `unique_ptr` (exclusive), `shared_ptr` (reference-counted), `weak_ptr` (non-owning)
|
||||
- **Move Semantics**: Rvalue references (`&&`) and `std::move` for efficient resource transfer
|
||||
- **Operator Overloading**: Define custom behavior for operators on user-defined types
|
||||
- **Virtual Functions and Vtable**: Runtime polymorphism through virtual method dispatch tables
|
||||
- **Namespaces**: Organize symbols and prevent name collisions across translation units
|
||||
- **Constexpr**: Compile-time evaluation of functions and variables for zero-runtime-cost computation
|
||||
- **Lambda Expressions**: Anonymous functions with capture lists for closures
|
||||
- **STL Containers and Algorithms**: Standard containers (vector, map, set) and generic algorithms
|
||||
- **Concepts (C++20)**: Named constraints on template parameters replacing SFINAE patterns
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `#include <system_header>` — include standard library or system headers
|
||||
- `#include "local_header.h"` — include project-local header files
|
||||
- `using namespace std` — bring all names from std into scope (avoid in headers)
|
||||
- `using std::vector` — selectively bring specific names into scope
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `.h` / `.hpp` — header files containing declarations, templates, and inline definitions
|
||||
- `.cpp` / `.cc` — implementation files with function definitions and static data
|
||||
- `CMakeLists.txt` — CMake build system configuration
|
||||
- `Makefile` — Make-based build rules and targets
|
||||
- `main.cpp` — program entry point containing `int main()`
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Qt** — Cross-platform application framework with signal/slot mechanism
|
||||
- **Boost** — Extensive collection of peer-reviewed portable libraries
|
||||
- **Catch2** — Header-only testing framework with BDD-style syntax
|
||||
- **Google Test** — Testing framework with fixtures, assertions, and mocking
|
||||
- **gRPC** — High-performance RPC framework for service communication
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses `std::unique_ptr<T>` for RAII-based ownership, ensuring deterministic cleanup
|
||||
> when scope exits. The unique pointer cannot be copied, only moved, making ownership
|
||||
> transfer explicit and preventing accidental double-free errors.
|
||||
>
|
||||
> Header/implementation separation (`.h`/`.cpp`) controls compilation boundaries —
|
||||
> changes to a `.cpp` file only recompile that translation unit, not all includers.
|
||||
46
.qoder/skills/understand/languages/csharp.md
Normal file
46
.qoder/skills/understand/languages/csharp.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# C# Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **LINQ Queries**: Language-integrated queries using method syntax (`.Where().Select()`) or query syntax
|
||||
- **Async/Await with Task**: Asynchronous programming model returning `Task<T>` for non-blocking I/O
|
||||
- **Generics and Constraints**: Type parameters with `where T : class, IDisposable` constraint clauses
|
||||
- **Properties (get/set)**: First-class property syntax with backing fields, auto-properties, and init-only
|
||||
- **Delegates and Events**: Type-safe function pointers; events provide publisher-subscriber pattern
|
||||
- **Attributes**: Metadata annotations (`[HttpGet]`, `[Authorize]`) for declarative configuration
|
||||
- **Nullable Reference Types**: Compiler-enforced null safety with `?` annotations (C# 8+)
|
||||
- **Pattern Matching**: `is`, `switch` expressions with type, property, and relational patterns
|
||||
- **Records and Init-Only Setters**: Immutable reference types with value equality semantics (C# 9+)
|
||||
- **Dependency Injection (Built-in)**: First-class DI container in ASP.NET Core (`IServiceCollection`)
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `using System.Collections.Generic` — import a namespace for unqualified type access
|
||||
- `using static System.Math` — import static members for direct method access
|
||||
- `global using` — file-scoped usings applied to the entire project (C# 10)
|
||||
- `using Alias = Namespace.Type` — type alias for disambiguation
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `*.csproj` — MSBuild project file defining targets, packages, and build properties
|
||||
- `*.sln` — Visual Studio solution file grouping multiple projects
|
||||
- `Program.cs` — application entry point (top-level statements in .NET 6+)
|
||||
- `Startup.cs` — service and middleware configuration (older ASP.NET Core pattern)
|
||||
- `appsettings.json` — hierarchical application configuration
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **ASP.NET Core** — Cross-platform web framework for APIs, MVC, and Razor Pages
|
||||
- **Entity Framework** — ORM with LINQ-to-SQL, migrations, and change tracking
|
||||
- **Blazor** — Component-based UI framework using C# instead of JavaScript
|
||||
- **MAUI** — Cross-platform native UI for mobile and desktop applications
|
||||
- **xUnit** — Modern testing framework with theories, facts, and dependency injection
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses LINQ method syntax `.Where().Select()` to compose a query pipeline over the
|
||||
> collection. LINQ operations are lazily evaluated — the query only executes when
|
||||
> results are enumerated, allowing efficient composition without intermediate allocations.
|
||||
>
|
||||
> The built-in DI container in ASP.NET Core registers services in `Program.cs` and
|
||||
> resolves them via constructor injection, following the composition root pattern.
|
||||
37
.qoder/skills/understand/languages/css.md
Normal file
37
.qoder/skills/understand/languages/css.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# CSS Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Selectors**: Element, class (`.name`), ID (`#name`), attribute (`[attr]`), and pseudo-class (`:hover`) targeting
|
||||
- **Specificity**: Inline > ID > Class > Element cascade priority determining which rules win
|
||||
- **Box Model**: `margin`, `border`, `padding`, `content` dimensions controlling element sizing
|
||||
- **Flexbox**: `display: flex` with `justify-content`, `align-items` for one-dimensional layouts
|
||||
- **Grid**: `display: grid` with `grid-template-columns/rows` for two-dimensional layouts
|
||||
- **Custom Properties (Variables)**: `--name: value` with `var(--name)` for reusable design tokens
|
||||
- **Media Queries**: `@media (max-width: ...)` for responsive design breakpoints
|
||||
- **SCSS/Sass Features**: Nesting, `$variables`, `@mixin`, `@include`, `@extend`, `@use`, `@forward`
|
||||
- **CSS Modules**: Scoped class names (`.module.css`) preventing global style collisions
|
||||
- **Cascade Layers**: `@layer` for explicit control over cascade ordering
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `*.css` — Standard CSS stylesheets
|
||||
- `*.scss` / `*.sass` — Sass/SCSS preprocessor files
|
||||
- `*.less` — Less preprocessor files
|
||||
- `*.module.css` / `*.module.scss` — CSS Modules (scoped styles)
|
||||
- `globals.css` / `reset.css` / `normalize.css` — Global base styles
|
||||
- `tailwind.config.js` — Tailwind CSS configuration (though a JS file)
|
||||
- `variables.scss` / `_variables.scss` — Design token definitions
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- CSS files are `related` to the HTML or component files that import them for styling
|
||||
- SCSS partial files (`_*.scss`) are `depends_on` by the main stylesheet that `@use`s them
|
||||
- CSS variable definition files are `related` to all stylesheets that reference those variables
|
||||
- CSS Modules are `related` to the component files that import them
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Global stylesheet defining CSS custom properties for the design system color palette and typography."
|
||||
> "Responsive layout styles with flexbox and grid for the dashboard page across 3 breakpoints."
|
||||
> "SCSS partial defining shared mixins for spacing, shadows, and media query breakpoints."
|
||||
34
.qoder/skills/understand/languages/dockerfile.md
Normal file
34
.qoder/skills/understand/languages/dockerfile.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Dockerfile Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Multi-Stage Builds**: Multiple `FROM` statements to separate build and runtime stages, reducing image size
|
||||
- **Layer Caching**: Each instruction creates a layer; order instructions from least to most frequently changing for cache efficiency
|
||||
- **Base Images**: `FROM image:tag` selects the starting image; prefer slim/alpine variants for smaller images
|
||||
- **COPY vs ADD**: `COPY` for local files (preferred), `ADD` for URLs and tar extraction
|
||||
- **Build Arguments**: `ARG` for build-time variables, `ENV` for runtime environment variables
|
||||
- **Health Checks**: `HEALTHCHECK` instruction for container orchestrator readiness probes
|
||||
- **Entry Point vs CMD**: `ENTRYPOINT` sets the executable, `CMD` provides default arguments
|
||||
- **User Permissions**: `USER` instruction to run as non-root for security
|
||||
- **Ignore Patterns**: `.dockerignore` excludes files from the build context (like `.gitignore`)
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `Dockerfile` — Primary container image definition (at project root)
|
||||
- `Dockerfile.dev` / `Dockerfile.prod` — Environment-specific Dockerfiles
|
||||
- `docker-compose.yml` — Multi-container application orchestration
|
||||
- `docker-compose.override.yml` — Local development overrides
|
||||
- `.dockerignore` — Build context exclusion patterns
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- Dockerfile `deploys` the application entry point it packages (COPY/CMD target)
|
||||
- docker-compose `depends_on` Dockerfile(s) it references for building
|
||||
- Dockerfile `depends_on` package manifests (package.json, requirements.txt) it copies for dependency installation
|
||||
- docker-compose services create `related` edges between co-deployed components
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Multi-stage Docker build producing a minimal Node.js production image with N build stages."
|
||||
> "Docker Compose configuration orchestrating N services with shared networking and persistent volumes."
|
||||
> "Development Dockerfile with hot-reload support and mounted source volumes."
|
||||
47
.qoder/skills/understand/languages/go.md
Normal file
47
.qoder/skills/understand/languages/go.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Go Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Goroutines**: Lightweight concurrent functions launched with `go` keyword
|
||||
- **Channels**: Typed conduits for communication and synchronization between goroutines
|
||||
- **Interfaces**: Implicitly satisfied contracts — no `implements` keyword needed
|
||||
- **Struct Embedding**: Composition mechanism providing field and method promotion
|
||||
- **Error Handling**: Explicit error return values (`error` interface) instead of exceptions
|
||||
- **Defer/Panic/Recover**: Deferred cleanup, unrecoverable errors, and recovery mechanism
|
||||
- **Slices vs Arrays**: Arrays are fixed-size values; slices are dynamic views backed by arrays
|
||||
- **Pointers**: Explicit pointer types for pass-by-reference semantics (no pointer arithmetic)
|
||||
- **Context Propagation**: `context.Context` carries deadlines, cancellation, and request-scoped values
|
||||
- **Init Functions**: Package-level `init()` runs automatically before `main()` for setup
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `import "package"` — single package import
|
||||
- `import alias "package"` — aliased import to avoid name conflicts
|
||||
- `import ( ... )` — grouped import block (standard library, then external, then internal)
|
||||
- `import _ "package"` — blank import for side effects only (e.g., driver registration)
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `*_test.go` — test files in the same package (or `_test` package for black-box tests)
|
||||
- `cmd/` — directory containing main packages (binary entry points)
|
||||
- `internal/` — packages only importable by parent module (enforced by compiler)
|
||||
- `pkg/` — public library packages (convention, not enforced)
|
||||
- `go.mod` — module definition with dependency versions
|
||||
- `go.sum` — cryptographic checksums for dependencies
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Gin** — High-performance HTTP framework with middleware support
|
||||
- **Echo** — Minimalist web framework with built-in middleware
|
||||
- **Fiber** — Express-inspired framework built on fasthttp
|
||||
- **Chi** — Lightweight, composable HTTP router
|
||||
- **GORM** — ORM library with associations, hooks, and migrations
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Implements `io.Reader` interface implicitly — no explicit declaration needed, just
|
||||
> matching method signatures. This enables any type with a `Read([]byte) (int, error)`
|
||||
> method to be used wherever `io.Reader` is expected.
|
||||
>
|
||||
> The `internal/` directory enforces encapsulation at the compiler level, preventing
|
||||
> external packages from importing implementation details — stronger than naming convention.
|
||||
35
.qoder/skills/understand/languages/graphql.md
Normal file
35
.qoder/skills/understand/languages/graphql.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# GraphQL Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Type System**: Strongly typed schema defining the API contract with scalar, object, enum, and union types
|
||||
- **Queries**: Read operations fetching data with field-level selection (no over-fetching)
|
||||
- **Mutations**: Write operations for creating, updating, and deleting data
|
||||
- **Subscriptions**: Real-time data push over WebSocket connections
|
||||
- **Resolvers**: Functions mapping schema fields to data sources (database, API, cache)
|
||||
- **Fragments**: Reusable field selections reducing query duplication across operations
|
||||
- **Directives**: `@deprecated`, `@include`, `@skip` for conditional field inclusion and schema metadata
|
||||
- **Input Types**: `input` keyword for complex mutation arguments
|
||||
- **Interfaces and Unions**: Polymorphic types for shared fields across multiple object types
|
||||
- **Schema Stitching / Federation**: Composing multiple GraphQL services into a unified graph
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `schema.graphql` / `*.graphql` — Schema definition files
|
||||
- `*.gql` — Alternative extension for GraphQL files
|
||||
- `schema/*.graphql` — Split schema files by domain (users.graphql, orders.graphql)
|
||||
- `*.resolvers.ts` / `*.resolvers.js` — Resolver implementations (TypeScript/JavaScript convention)
|
||||
- `codegen.yml` — GraphQL Code Generator configuration
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- GraphQL schema files `defines_schema` for the resolver code that implements query/mutation handlers
|
||||
- Type definitions create `related` edges between types connected by field references
|
||||
- Schema files `defines_schema` for client-side query/mutation files that consume the API
|
||||
- Codegen config `configures` the schema-to-code generation pipeline
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "GraphQL schema defining N types, M queries, and K mutations for the user management API."
|
||||
> "API schema with type definitions for products, orders, and payment processing with pagination."
|
||||
> "Subscription schema enabling real-time notifications for order status updates."
|
||||
34
.qoder/skills/understand/languages/html.md
Normal file
34
.qoder/skills/understand/languages/html.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# HTML Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Semantic Elements**: `<main>`, `<nav>`, `<header>`, `<footer>`, `<article>`, `<section>` for meaningful structure
|
||||
- **Document Structure**: `<!DOCTYPE html>`, `<html>`, `<head>`, `<body>` forming the page skeleton
|
||||
- **Forms**: `<form>`, `<input>`, `<select>`, `<textarea>` for user data collection with validation attributes
|
||||
- **Accessibility**: `aria-*` attributes, `role`, `alt` text, and semantic markup for screen readers
|
||||
- **Meta Tags**: `<meta>` for viewport, charset, description, Open Graph, and SEO metadata
|
||||
- **Script and Style Loading**: `<script>`, `<link>`, `<style>` for JavaScript and CSS inclusion
|
||||
- **Data Attributes**: `data-*` custom attributes for storing element-specific data
|
||||
- **Template Syntax**: Framework-specific templating (`{{ }}` for Jinja/Django, `<%= %>` for ERB)
|
||||
- **Web Components**: `<template>`, `<slot>`, Custom Elements for encapsulated reusable components
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `index.html` — Application entry point or SPA shell
|
||||
- `*.html` / `*.htm` — Static HTML pages
|
||||
- `templates/**/*.html` — Server-side template files (Django, Jinja2, Go templates)
|
||||
- `public/index.html` — SPA root document (React, Vue)
|
||||
- `*.ejs` / `*.hbs` / `*.pug` — Templating engine files
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- HTML files `depends_on` JavaScript and CSS files they include via `<script>` and `<link>` tags
|
||||
- Template HTML files `depends_on` the server-side code that renders them
|
||||
- HTML entry points are `deploys` targets for build systems and web servers
|
||||
- HTML files `related` to the components or routes they render
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Single-page application shell with viewport meta, CSS reset, and React root mount point."
|
||||
> "Server-rendered template with navigation, content area, and footer using Django template inheritance."
|
||||
> "Static landing page with responsive layout, form, and third-party script integrations."
|
||||
45
.qoder/skills/understand/languages/java.md
Normal file
45
.qoder/skills/understand/languages/java.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Java Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Generics (with Erasure)**: Parameterized types erased at runtime; compile-time safety only
|
||||
- **Annotations**: Metadata markers (`@Override`, `@Autowired`) processed at compile or runtime
|
||||
- **Interfaces and Abstract Classes**: Contracts with default methods (Java 8+) and partial implementations
|
||||
- **Streams API**: Functional-style pipeline operations on collections (filter, map, reduce)
|
||||
- **Lambdas**: Concise anonymous function syntax for functional interfaces
|
||||
- **Sealed Classes**: Restricted class hierarchies with explicit permitted subclasses (Java 17+)
|
||||
- **Records**: Immutable data carriers with auto-generated accessors, equals, hashCode (Java 16+)
|
||||
- **Dependency Injection**: IoC pattern central to Spring; constructor, field, or method injection
|
||||
- **Checked vs Unchecked Exceptions**: Checked must be declared or caught; unchecked extend RuntimeException
|
||||
- **Optional**: Container for nullable values encouraging explicit handling over null checks
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `import package.Class` — import a specific class
|
||||
- `import package.*` — wildcard import of all classes in a package
|
||||
- `import static package.Class.method` — static import for direct method/constant access
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `src/main/java/` — source root following Maven/Gradle standard layout
|
||||
- `src/test/java/` — test source root with matching package structure
|
||||
- `pom.xml` — Maven project configuration and dependency management
|
||||
- `build.gradle` — Gradle build script (Groovy or Kotlin DSL)
|
||||
- `Application.java` — Spring Boot entry point with `@SpringBootApplication`
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Spring Boot** — Opinionated framework for production-ready Spring applications
|
||||
- **Jakarta EE** — Enterprise Java standards (formerly Java EE) for server-side development
|
||||
- **Quarkus** — Cloud-native framework optimized for GraalVM and containers
|
||||
- **Micronaut** — Compile-time DI framework for microservices and serverless
|
||||
- **Hibernate** — ORM framework implementing JPA specification
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses `@Autowired` annotation for constructor injection, following Spring IoC container
|
||||
> pattern. Constructor injection is preferred over field injection because it makes
|
||||
> dependencies explicit and enables immutability.
|
||||
>
|
||||
> The Maven standard directory layout (`src/main/java`, `src/test/java`) is a strong
|
||||
> convention — most build tools and IDEs expect this structure by default.
|
||||
46
.qoder/skills/understand/languages/javascript.md
Normal file
46
.qoder/skills/understand/languages/javascript.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# JavaScript Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Closures**: Functions that capture variables from their enclosing lexical scope
|
||||
- **Prototypes**: Prototype chain-based inheritance underlying all JavaScript objects
|
||||
- **Promises**: Asynchronous value containers enabling `.then()` chaining and `async/await`
|
||||
- **Event Loop**: Single-threaded concurrency model with microtask and macrotask queues
|
||||
- **Destructuring**: Extract values from objects and arrays into distinct variables
|
||||
- **Spread/Rest Operators**: `...` for expanding iterables or collecting remaining arguments
|
||||
- **Proxies**: Meta-programming construct to intercept and customize object operations
|
||||
- **Generators**: Functions using `function*` and `yield` for lazy iteration
|
||||
- **Symbol**: Unique, immutable primitive used for non-string property keys
|
||||
- **WeakMap/WeakSet**: Collections with weakly-held keys allowing garbage collection
|
||||
- **Modules (ESM vs CJS)**: ES Modules use `import/export`; CommonJS uses `require/module.exports`
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `import { X } from 'module'` — ESM named import
|
||||
- `const X = require('module')` — CommonJS require
|
||||
- `import('module')` — dynamic import returning a Promise (code splitting)
|
||||
- `export default X` / `export { X }` — ESM export forms
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `index.js` — barrel file or directory entry point
|
||||
- `.mjs` — explicitly ES Module files
|
||||
- `.cjs` — explicitly CommonJS files
|
||||
- `package.json` `"type"` field — sets default module system (`"module"` or `"commonjs"`)
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **React** — Declarative UI with virtual DOM and component model
|
||||
- **Vue** — Progressive framework with reactivity system and single-file components
|
||||
- **Express** — Minimal and flexible Node.js web application framework
|
||||
- **Next.js** — React framework for production with hybrid rendering
|
||||
- **Svelte** — Compile-time framework that shifts work from runtime to build step
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Closure captures outer `config` variable, providing encapsulated state without class
|
||||
> overhead. The returned object's methods share access to the same `config` reference,
|
||||
> forming a module pattern that was standard before ES Modules.
|
||||
>
|
||||
> When encountering `.mjs` vs `.cjs` extensions, the module system is determined by
|
||||
> extension regardless of the `package.json` type field — useful in mixed codebases.
|
||||
34
.qoder/skills/understand/languages/json.md
Normal file
34
.qoder/skills/understand/languages/json.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# JSON Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Strict Syntax**: No trailing commas, no comments (unlike JSONC or JSON5), double-quoted strings only
|
||||
- **Data Types**: Objects, arrays, strings, numbers, booleans, and null — no undefined or date types
|
||||
- **Nested Structure**: Arbitrary nesting depth for hierarchical configuration or data
|
||||
- **Schema Validation**: JSON Schema (`$schema` keyword) for validating structure and types
|
||||
- **JSONC**: JSON with Comments variant used by VS Code, tsconfig.json, and other tooling
|
||||
- **JSON5**: Extended JSON allowing comments, trailing commas, unquoted keys, and more
|
||||
- **JSON Lines** (`.jsonl`): One JSON object per line for streaming data processing
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `package.json` — Node.js project manifest with dependencies, scripts, and metadata
|
||||
- `tsconfig.json` — TypeScript compiler configuration (actually JSONC)
|
||||
- `.eslintrc.json` — ESLint linting rules and configuration
|
||||
- `*.schema.json` — JSON Schema definitions for validation
|
||||
- `composer.json` — PHP Composer project manifest
|
||||
- `appsettings.json` — .NET application configuration
|
||||
- `manifest.json` — Browser extension or PWA manifest
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- `package.json` `configures` the build toolchain and defines project dependencies
|
||||
- `tsconfig.json` `configures` TypeScript compilation for all `.ts` files
|
||||
- JSON Schema files `defines_schema` for API request/response validation
|
||||
- Config JSON files `configures` the runtime behavior of the application
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Node.js project manifest defining N dependencies, build scripts, and project metadata."
|
||||
> "TypeScript compiler configuration enabling strict mode with path aliases for monorepo packages."
|
||||
> "JSON Schema defining the request/response structure for the user API endpoint."
|
||||
45
.qoder/skills/understand/languages/kotlin.md
Normal file
45
.qoder/skills/understand/languages/kotlin.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Kotlin Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Coroutines and Flow**: Structured concurrency with suspending functions; Flow for reactive streams
|
||||
- **Data Classes**: Auto-generated `equals`, `hashCode`, `toString`, `copy`, and destructuring
|
||||
- **Sealed Classes/Interfaces**: Restricted hierarchies enabling exhaustive `when` expressions
|
||||
- **Extension Functions**: Add methods to existing classes without inheritance or wrappers
|
||||
- **Null Safety**: `?.` safe call, `!!` non-null assertion, `?:` Elvis operator for default values
|
||||
- **Delegation (by keyword)**: Delegate interface implementation or property access to another object
|
||||
- **DSL Builders**: Lambda-with-receiver syntax enabling type-safe builder patterns
|
||||
- **Inline Functions and Reified Types**: Inline for zero-overhead lambdas; reified for runtime type access
|
||||
- **Companion Objects**: Named or anonymous singleton associated with a class (replaces static members)
|
||||
- **Scope Functions**: `let`, `run`, `apply`, `also`, `with` for concise object configuration and transformation
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `import package.ClassName` — import a specific class
|
||||
- `import package.*` — wildcard import of all declarations in a package
|
||||
- `import package.function as alias` — import with alias to resolve naming conflicts
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `build.gradle.kts` — Gradle build script using Kotlin DSL
|
||||
- `Application.kt` — application entry point (Spring Boot or Ktor)
|
||||
- `src/main/kotlin/` — main source root following Gradle conventions
|
||||
- `src/test/kotlin/` — test source root with matching package structure
|
||||
- `settings.gradle.kts` — multi-module project configuration
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Spring Boot (Kotlin)** — Kotlin-first support with coroutines and DSL extensions
|
||||
- **Ktor** — Kotlin-native async web framework from JetBrains
|
||||
- **Jetpack Compose** — Declarative UI toolkit for Android using composable functions
|
||||
- **Exposed** — Lightweight SQL framework with type-safe DSL and DAO patterns
|
||||
- **Koin** — Pragmatic dependency injection framework using Kotlin DSL
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses sealed class hierarchy with `when` exhaustive matching to handle all possible
|
||||
> API response states. The compiler enforces that every variant is covered, eliminating
|
||||
> the need for a fallback `else` branch and catching missing cases at compile time.
|
||||
>
|
||||
> Extension functions allow adding utilities like `String.toSlug()` without modifying
|
||||
> the original class — keeping the extension discoverable through IDE auto-complete.
|
||||
34
.qoder/skills/understand/languages/markdown.md
Normal file
34
.qoder/skills/understand/languages/markdown.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Markdown Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Heading Hierarchy**: `#` through `######` for document structure, with h1 as the title
|
||||
- **Front Matter**: YAML metadata between `---` delimiters at the top of the file
|
||||
- **Fenced Code Blocks**: Triple backticks with optional language identifier for syntax highlighting
|
||||
- **Reference-Style Links**: `[text][ref]` with `[ref]: url` definitions, useful for repeated URLs
|
||||
- **Tables**: Pipe-delimited columns with alignment markers (`:---`, `:---:`, `---:`)
|
||||
- **Admonitions**: Blockquote-based callouts (`> **Note:**`, `> **Warning:**`) for emphasis
|
||||
- **Task Lists**: `- [ ]` and `- [x]` for checklists in issue trackers and READMEs
|
||||
- **HTML Embedding**: Raw HTML allowed inline for features Markdown does not support natively
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `README.md` — Project overview and entry point for new contributors (high-value)
|
||||
- `CONTRIBUTING.md` — Contribution guidelines, code style, PR process
|
||||
- `CHANGELOG.md` — Version history following Keep a Changelog or similar format
|
||||
- `docs/**/*.md` — Documentation directory with guides, API references, tutorials
|
||||
- `*.md` in source directories — Co-located documentation for modules or packages
|
||||
- `ADR-*.md` or `adr/*.md` — Architecture Decision Records
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- Markdown files `documents` the code components they describe or reference
|
||||
- Links to other `.md` files create `related` edges between documentation nodes
|
||||
- Code block references mentioning file paths may imply `documents` edges to those files
|
||||
- README files in subdirectories typically `documents` the module at that path
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Project overview documentation with N sections covering installation, usage, and API reference."
|
||||
> "Architecture Decision Record documenting the choice of [technology] for [purpose]."
|
||||
> "Contributing guide with code style rules, testing requirements, and pull request process."
|
||||
46
.qoder/skills/understand/languages/php.md
Normal file
46
.qoder/skills/understand/languages/php.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# PHP Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Namespaces**: Organize code and prevent naming collisions using backslash-delimited paths
|
||||
- **Traits**: Horizontal code reuse mechanism for sharing methods across unrelated classes
|
||||
- **Type Declarations**: Parameter, return, and property types (scalar, union, intersection types)
|
||||
- **Attributes (PHP 8+)**: Native metadata annotations replacing docblock-based configuration
|
||||
- **Enums (PHP 8.1+)**: First-class enumeration types with methods and interface implementation
|
||||
- **Fibers**: Lightweight cooperative concurrency primitives for non-blocking I/O
|
||||
- **Closures/Anonymous Functions**: First-class functions with explicit `use` for variable capture
|
||||
- **Magic Methods**: Special methods like `__construct`, `__get`, `__set`, `__call` for object behavior
|
||||
- **Dependency Injection**: Constructor injection managed by PSR-11 compatible containers
|
||||
- **Middleware**: Request/response pipeline pattern central to modern PHP frameworks
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `use Namespace\ClassName` — import a class by its fully qualified name
|
||||
- `use Namespace\ClassName as Alias` — import with an alias to avoid conflicts
|
||||
- `namespace App\Http\Controllers` — declare the current file's namespace
|
||||
- `use function Namespace\functionName` — import a namespaced function
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `composer.json` — dependency management and PSR-4 autoloading configuration
|
||||
- `index.php` — web application entry point (front controller)
|
||||
- `artisan` — Laravel CLI entry point for commands and migrations
|
||||
- `routes/` — route definition files (web.php, api.php in Laravel)
|
||||
- PSR-4 autoloading maps namespace prefixes to directory paths
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Laravel** — Full-featured framework with Eloquent ORM, Blade templates, and queues
|
||||
- **Symfony** — Component-based framework powering many PHP projects and libraries
|
||||
- **WordPress** — CMS platform with hook-based plugin architecture
|
||||
- **Slim** — Micro-framework for APIs and small applications
|
||||
- **CodeIgniter** — Lightweight MVC framework with minimal configuration
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses PHP 8 attributes `#[Route('/api/users')]` for declarative route mapping on
|
||||
> controller methods. Attributes replace the older docblock annotation pattern,
|
||||
> providing native language support for metadata that tools can reflect upon.
|
||||
>
|
||||
> PSR-4 autoloading in `composer.json` maps `App\` to `src/`, so the class
|
||||
> `App\Http\Controllers\UserController` loads from `src/Http/Controllers/UserController.php`.
|
||||
34
.qoder/skills/understand/languages/protobuf.md
Normal file
34
.qoder/skills/understand/languages/protobuf.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Protobuf Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Message Types**: `message` blocks defining structured data with typed, numbered fields
|
||||
- **Field Numbers**: Permanent identifiers (1-536870911) — never reuse deleted numbers for backward compatibility
|
||||
- **Scalar Types**: `int32`, `int64`, `string`, `bytes`, `bool`, `float`, `double`, and more
|
||||
- **Enums**: Named integer constants for categorical values
|
||||
- **Services**: `service` blocks defining RPC (Remote Procedure Call) method signatures
|
||||
- **Oneof**: Mutually exclusive field groups — only one field in the group can be set
|
||||
- **Repeated Fields**: `repeated` keyword for list/array fields
|
||||
- **Maps**: `map<key_type, value_type>` for dictionary/hash fields
|
||||
- **Packages and Imports**: Namespace organization and cross-file references
|
||||
- **Proto2 vs Proto3**: Proto3 (current) removes required/optional distinction and defaults all fields
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `*.proto` — Protocol Buffer definition files
|
||||
- `proto/**/*.proto` — Organized proto definitions by service or domain
|
||||
- `buf.yaml` / `buf.gen.yaml` — Buf tool configuration for linting and code generation
|
||||
- `*_pb2.py` / `*.pb.go` / `*_pb.ts` — Generated code (should be excluded from analysis)
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- Protobuf files `defines_schema` for the gRPC service handlers that implement the declared RPCs
|
||||
- Message type references create `related` edges between proto files sharing types
|
||||
- Proto `import` statements create `depends_on` edges between proto files
|
||||
- Generated code files are `depends_on` the proto source that produces them
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Protocol Buffer definitions for N message types and M RPC services in the user authentication domain."
|
||||
> "Shared proto types defining common request/response envelopes and error codes."
|
||||
> "gRPC service definition with N methods for real-time data streaming and batch processing."
|
||||
48
.qoder/skills/understand/languages/python.md
Normal file
48
.qoder/skills/understand/languages/python.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Python Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Decorators**: Functions that wrap other functions or classes using `@decorator` syntax
|
||||
- **List/Dict Comprehensions**: Concise syntax for creating collections from iterables
|
||||
- **Generators and Yield**: Lazy iterators using `yield` for memory-efficient data processing
|
||||
- **Context Managers**: `with` statement for resource management via `__enter__`/`__exit__`
|
||||
- **Type Hints and Typing Module**: Optional static type annotations for tooling and documentation
|
||||
- **Dunder Methods**: Special methods like `__init__`, `__repr__`, `__eq__` defining object behavior
|
||||
- **Metaclasses**: Classes that define how other classes are created (type as default metaclass)
|
||||
- **Dataclasses**: `@dataclass` decorator auto-generating boilerplate from field annotations
|
||||
- **Protocols**: Structural subtyping via `typing.Protocol` for duck-type-safe interfaces
|
||||
- **Descriptors**: Objects defining `__get__`, `__set__`, `__delete__` to customize attribute access
|
||||
- **Async/Await with Asyncio**: Cooperative concurrency using coroutines and an event loop
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `from module import name` — import specific name from module
|
||||
- `import module` — import entire module, access via `module.name`
|
||||
- `from package.module import name` — absolute import from nested package
|
||||
- `from . import relative` — relative import within a package
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `__init__.py` — package initializer (barrel equivalent), can re-export public API
|
||||
- `__main__.py` — package entry point when run with `python -m package`
|
||||
- `conftest.py` — pytest shared fixtures and hooks (auto-discovered)
|
||||
- `setup.py` / `pyproject.toml` — project configuration and build metadata
|
||||
- `requirements.txt` — pinned dependency list
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Django** — Full-stack web framework with ORM, admin, and batteries included
|
||||
- **FastAPI** — Modern async API framework with automatic OpenAPI docs
|
||||
- **Flask** — Lightweight WSGI micro-framework for web applications
|
||||
- **SQLAlchemy** — SQL toolkit and ORM with unit-of-work pattern
|
||||
- **Celery** — Distributed task queue for background job processing
|
||||
- **Pydantic** — Data validation and settings management using type annotations
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses `@dataclass` decorator to auto-generate `__init__`, `__repr__`, and `__eq__` from
|
||||
> field annotations. This eliminates boilerplate while keeping the class definition
|
||||
> readable and the generated methods consistent.
|
||||
>
|
||||
> When `__init__.py` re-exports symbols, it acts as the package's public API surface —
|
||||
> consumers import from the package rather than reaching into internal modules.
|
||||
46
.qoder/skills/understand/languages/ruby.md
Normal file
46
.qoder/skills/understand/languages/ruby.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Ruby Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Blocks/Procs/Lambdas**: First-class callable objects; blocks are implicit, procs and lambdas are explicit
|
||||
- **Mixins (include/extend)**: Share behavior across classes via modules without inheritance
|
||||
- **Metaprogramming**: Dynamic method definition (`define_method`), interception (`method_missing`)
|
||||
- **Duck Typing**: Objects are defined by what they can do, not what class they are
|
||||
- **DSLs**: Domain-specific languages built using blocks and metaprogramming (e.g., Rails routes)
|
||||
- **Monkey Patching**: Reopening existing classes to add or modify methods at runtime
|
||||
- **Symbols**: Immutable, interned strings (`:name`) used as identifiers and hash keys
|
||||
- **Open Classes**: Any class can be reopened and extended at any point in the program
|
||||
- **Enumerable Module**: Mixin providing collection methods (map, select, reduce) to any class with `each`
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `require 'gem_name'` — load a gem or standard library module
|
||||
- `require_relative './file'` — load a file relative to the current file's directory
|
||||
- `load 'file.rb'` — load and re-execute a file (unlike require, does not cache)
|
||||
- `autoload :ClassName, 'path'` — lazy loading of constants on first reference
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `Gemfile` — dependency declarations managed by Bundler
|
||||
- `Rakefile` — task definitions (Ruby's make equivalent)
|
||||
- `spec/` — RSpec test directory with `*_spec.rb` convention
|
||||
- `test/` — Minitest directory with `test_*.rb` or `*_test.rb` convention
|
||||
- `config.ru` — Rack application entry point for web servers
|
||||
- `lib/` — main source code directory by convention
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Rails** — Full-stack web framework following convention over configuration
|
||||
- **Sinatra** — Minimal DSL for creating web applications quickly
|
||||
- **RSpec** — Behavior-driven testing framework with expressive DSL
|
||||
- **Sidekiq** — Background job processing using Redis-backed queues
|
||||
- **Grape** — REST API micro-framework for Ruby
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses `method_missing` to dynamically delegate attribute access to the wrapped model
|
||||
> object. When a method is not found on the decorator, it falls through to the model,
|
||||
> providing transparent delegation without explicit forwarding methods.
|
||||
>
|
||||
> Rails relies heavily on convention over configuration — file placement in `app/models/`,
|
||||
> `app/controllers/`, etc. determines behavior without explicit registration.
|
||||
47
.qoder/skills/understand/languages/rust.md
Normal file
47
.qoder/skills/understand/languages/rust.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Rust Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Ownership and Borrowing**: Each value has one owner; references borrow without taking ownership
|
||||
- **Lifetimes**: Annotations (`'a`) ensuring references remain valid for their required duration
|
||||
- **Traits and Trait Objects**: Shared behavior definitions; `dyn Trait` for dynamic dispatch
|
||||
- **Pattern Matching**: Exhaustive `match` expressions deconstructing enums, structs, and tuples
|
||||
- **Enums with Data**: Algebraic data types — each variant can carry different associated data
|
||||
- **Result/Option Error Handling**: `Result<T, E>` for fallible ops; `Option<T>` for nullable values
|
||||
- **Macros**: Declarative (`macro_rules!`) and procedural (derive, attribute, function-like) code generation
|
||||
- **Async/Await with Tokio**: Zero-cost async using `Future` trait and runtime executors
|
||||
- **Unsafe Blocks**: Opt-in blocks for raw pointer dereferencing, FFI, and bypassing borrow checker
|
||||
- **Generics with Trait Bounds**: `<T: Clone + Send>` constraining generic parameters
|
||||
- **Closures and Fn Traits**: `Fn`, `FnMut`, `FnOnce` determine how closures capture environment
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `use crate::module::Item` — import from current crate
|
||||
- `use std::collections::HashMap` — import from standard library
|
||||
- `use super::*` — import everything from parent module
|
||||
- `mod module_name` — declare a submodule (loads from file)
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `mod.rs` — module barrel file (older convention) or `module_name.rs` (2018+ edition)
|
||||
- `lib.rs` — library crate root defining the public API
|
||||
- `main.rs` — binary crate entry point
|
||||
- `Cargo.toml` — project manifest with dependencies and metadata
|
||||
- `build.rs` — build script executed before compilation
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **Actix-web** — Actor-based, high-performance web framework
|
||||
- **Axum** — Ergonomic web framework built on Tower and Hyper
|
||||
- **Rocket** — Type-safe web framework with declarative routing
|
||||
- **Diesel** — Safe, composable ORM and query builder
|
||||
- **Tokio** — Async runtime providing I/O, timers, and task scheduling
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Takes `&self` borrow to read state without transferring ownership; returns
|
||||
> `Result<T, Error>` for explicit error propagation. The `?` operator propagates
|
||||
> errors up the call stack concisely, replacing verbose match blocks.
|
||||
>
|
||||
> The module system maps to the filesystem: `mod handlers;` loads either
|
||||
> `handlers.rs` or `handlers/mod.rs`, establishing the module tree at compile time.
|
||||
35
.qoder/skills/understand/languages/shell.md
Normal file
35
.qoder/skills/understand/languages/shell.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Shell Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Shebang Line**: `#!/bin/bash` or `#!/usr/bin/env bash` specifying the interpreter
|
||||
- **Variables**: `VAR=value` assignment, `$VAR` or `${VAR}` expansion, no spaces around `=`
|
||||
- **Functions**: `function name()` or `name()` for reusable command groups
|
||||
- **Conditionals**: `if [[ condition ]]; then ... fi` with `[[ ]]` for extended tests
|
||||
- **Loops**: `for item in list`, `while condition`, `until condition` iteration patterns
|
||||
- **Pipes and Redirection**: `|` for chaining commands, `>` / `>>` / `2>&1` for output redirection
|
||||
- **Exit Codes**: `$?` captures last command status; `set -e` exits on any failure
|
||||
- **Strict Mode**: `set -euo pipefail` for robust error handling (exit on error, undefined vars, pipe failures)
|
||||
- **Command Substitution**: `$(command)` captures command output as a string
|
||||
- **Here Documents**: `<<EOF ... EOF` for multi-line string input to commands
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `*.sh` / `*.bash` — Shell script files
|
||||
- `scripts/*.sh` — Project automation scripts (build, deploy, setup)
|
||||
- `entrypoint.sh` — Docker container entry point script
|
||||
- `install.sh` / `setup.sh` — Environment setup scripts
|
||||
- `.bashrc` / `.bash_profile` / `.zshrc` — Shell configuration files
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- Shell scripts `triggers` other scripts or build processes they invoke
|
||||
- Entry point scripts `deploys` the application they start
|
||||
- Setup scripts `configures` the development environment
|
||||
- Build scripts `depends_on` the source files they compile or package
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Build automation script compiling TypeScript, running tests, and packaging the release artifact."
|
||||
> "Docker entry point script handling signal forwarding and graceful shutdown."
|
||||
> "Environment setup script installing dependencies and configuring development tools."
|
||||
36
.qoder/skills/understand/languages/sql.md
Normal file
36
.qoder/skills/understand/languages/sql.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# SQL Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **DDL (Data Definition)**: `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE` for schema management
|
||||
- **DML (Data Manipulation)**: `SELECT`, `INSERT`, `UPDATE`, `DELETE` for data operations
|
||||
- **Normalization**: Organizing tables to reduce redundancy through 1NF, 2NF, 3NF relationships
|
||||
- **Foreign Keys**: `REFERENCES` constraints enforcing referential integrity between tables
|
||||
- **Indexes**: `CREATE INDEX` for query performance optimization on frequently queried columns
|
||||
- **Migrations**: Numbered, sequential schema changes applied in order for version control
|
||||
- **Transactions**: `BEGIN`/`COMMIT`/`ROLLBACK` for atomic multi-statement operations
|
||||
- **Views**: Named queries (`CREATE VIEW`) providing virtual tables for complex joins
|
||||
- **Stored Procedures**: Server-side functions for encapsulating business logic in the database
|
||||
- **Constraints**: `NOT NULL`, `UNIQUE`, `CHECK`, `DEFAULT` for data integrity rules
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `migrations/*.sql` — Numbered migration files (e.g., `001_create_users.sql`, `002_add_orders.sql`)
|
||||
- `schema.sql` — Full database schema definition (often generated from migrations)
|
||||
- `seeds/*.sql` — Seed data for development and testing environments
|
||||
- `*.up.sql` / `*.down.sql` — Reversible migration pairs (up applies, down reverts)
|
||||
- `init.sql` — Database initialization script for Docker or fresh setup
|
||||
- `procedures/*.sql` — Stored procedure definitions
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- SQL migration files `migrates` the tables they create or alter
|
||||
- Schema definition files `defines_schema` for the ORM models or data layer code that reads them
|
||||
- Table definitions create implicit `related` edges between tables connected by foreign keys
|
||||
- Seed files `depends_on` the migration files that create the tables they populate
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Database migration creating the users table with email, name, and authentication columns."
|
||||
> "Schema definition with N tables covering user management, orders, and payment processing."
|
||||
> "Seed data populating N tables with development fixtures for testing."
|
||||
46
.qoder/skills/understand/languages/swift.md
Normal file
46
.qoder/skills/understand/languages/swift.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Swift Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Optionals and Optional Chaining**: `Type?` wraps values that may be nil; `?.` chains safely
|
||||
- **Protocols and Protocol Extensions**: Define contracts with default implementations via extensions
|
||||
- **Value Types vs Reference Types**: Structs and enums are value types; classes are reference types
|
||||
- **Closures**: Self-contained blocks of functionality that capture surrounding context
|
||||
- **Property Wrappers**: `@State`, `@Binding`, `@Published` encapsulate property storage logic
|
||||
- **Result Builders**: `@ViewBuilder`, `@resultBuilder` enable declarative DSL syntax
|
||||
- **Actors and Structured Concurrency**: `actor` types for data isolation; `async let`, `TaskGroup`
|
||||
- **Generics**: Type parameters with `where` clauses and associated type constraints
|
||||
- **Enums with Associated Values**: Each case can carry distinct typed payloads
|
||||
- **Extensions**: Add methods, computed properties, and protocol conformance to existing types
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `import Foundation` — core library with data types, collections, networking
|
||||
- `import UIKit` — iOS UI framework for traditional view controller architecture
|
||||
- `import SwiftUI` — declarative UI framework with reactive state management
|
||||
- `@testable import ModuleName` — import with internal access for unit testing
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `Package.swift` — Swift Package Manager manifest defining targets and dependencies
|
||||
- `*.xcodeproj` / `*.xcworkspace` — Xcode project and workspace configuration
|
||||
- `AppDelegate.swift` — UIKit application lifecycle entry point
|
||||
- `App.swift` — SwiftUI application entry point using `@main`
|
||||
- `Tests/` — test target directory following SPM or Xcode conventions
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **SwiftUI** — Declarative UI framework with reactive data flow
|
||||
- **UIKit** — Imperative UI framework using view controllers and Auto Layout
|
||||
- **Vapor** — Server-side Swift web framework with async support
|
||||
- **Combine** — Reactive framework for processing values over time
|
||||
- **Core Data** — Object graph and persistence framework
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses `@Published` property wrapper to automatically notify SwiftUI views of state
|
||||
> changes. When the wrapped value mutates, the property wrapper triggers `objectWillChange`
|
||||
> on the enclosing `ObservableObject`, causing dependent views to re-render.
|
||||
>
|
||||
> Protocol extensions provide default implementations, allowing types to conform by
|
||||
> simply declaring conformance — no method body needed if defaults suffice.
|
||||
38
.qoder/skills/understand/languages/terraform.md
Normal file
38
.qoder/skills/understand/languages/terraform.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Terraform Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Declarative Infrastructure**: Define desired state; Terraform computes and applies the diff
|
||||
- **Providers**: Plugins connecting to cloud APIs (AWS, GCP, Azure, Kubernetes, etc.)
|
||||
- **Resources**: `resource "type" "name"` blocks declaring infrastructure components
|
||||
- **Data Sources**: `data "type" "name"` blocks reading existing infrastructure state
|
||||
- **Variables**: `variable` blocks for parameterizing configurations with defaults and validation
|
||||
- **Outputs**: `output` blocks exposing values for cross-module references or human consumption
|
||||
- **Modules**: Reusable, composable infrastructure packages with their own variables and outputs
|
||||
- **State Management**: `.tfstate` files tracking real-world resource mapping (never commit to git)
|
||||
- **Workspaces**: Isolated state environments for managing dev/staging/prod from one codebase
|
||||
- **Plan and Apply**: `terraform plan` previews changes, `terraform apply` executes them
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `main.tf` — Primary resource definitions
|
||||
- `variables.tf` — Input variable declarations with types and defaults
|
||||
- `outputs.tf` — Output value definitions
|
||||
- `providers.tf` — Provider configuration and version constraints
|
||||
- `backend.tf` — Remote state backend configuration (S3, GCS, etc.)
|
||||
- `modules/**/*.tf` — Reusable infrastructure modules
|
||||
- `*.tfvars` — Variable value files for different environments
|
||||
- `terraform.lock.hcl` — Provider version lock file
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- Terraform files `provisions` the infrastructure resources they define
|
||||
- Module references create `depends_on` edges between terraform files
|
||||
- Terraform `deploys` application code by referencing container images or deployment targets
|
||||
- Variable files `configures` the terraform modules they parameterize
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Terraform configuration provisioning N AWS resources including VPC, ECS cluster, and RDS instance."
|
||||
> "Infrastructure module defining a reusable Kubernetes namespace with RBAC and network policies."
|
||||
> "Variable definitions for N environment-specific settings (region, instance type, scaling)."
|
||||
46
.qoder/skills/understand/languages/typescript.md
Normal file
46
.qoder/skills/understand/languages/typescript.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# TypeScript Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Generics**: Parameterized types (`<T>`) enabling reusable, type-safe abstractions
|
||||
- **Type Guards**: Runtime checks that narrow types within conditional blocks (`is`, `in`, `typeof`, `instanceof`)
|
||||
- **Discriminated Unions**: Union types with a shared literal field used for exhaustive narrowing
|
||||
- **Utility Types**: Built-in mapped types like `Partial<T>`, `Pick<T, K>`, `Omit<T, K>`, `Record<K, V>`
|
||||
- **Interfaces vs Types**: Interfaces support declaration merging; type aliases support unions and mapped types
|
||||
- **Enums**: Numeric and string enums for named constant sets; prefer `as const` objects when possible
|
||||
- **Mapped Types**: Transform existing types property-by-property using `[K in keyof T]` syntax
|
||||
- **Conditional Types**: `T extends U ? X : Y` for type-level branching logic
|
||||
- **Template Literal Types**: String manipulation at the type level using backtick syntax
|
||||
- **Declaration Merging**: Interfaces with the same name merge their members automatically
|
||||
- **Module Augmentation**: Extending third-party module types via `declare module` blocks
|
||||
|
||||
## Import Patterns
|
||||
|
||||
- `import { X } from 'module'` — named import (most common)
|
||||
- `import type { X } from 'module'` — type-only import (erased at runtime)
|
||||
- `import * as X from 'module'` — namespace import
|
||||
- `import X from 'module'` — default import
|
||||
|
||||
## File Patterns
|
||||
|
||||
- `index.ts` — barrel file re-exporting public API from a directory
|
||||
- `*.d.ts` — type declaration files (ambient declarations, no runtime code)
|
||||
- `tsconfig.json` — TypeScript compiler configuration and project references
|
||||
- `*.tsx` — TypeScript files containing JSX (React components)
|
||||
|
||||
## Common Frameworks
|
||||
|
||||
- **React** — UI component library with hooks and JSX
|
||||
- **Angular** — Full-featured framework with decorators and dependency injection
|
||||
- **Next.js** — React meta-framework with SSR, SSG, and API routes
|
||||
- **NestJS** — Server-side framework inspired by Angular (decorators, modules, DI)
|
||||
- **Express (with TS)** — Minimal HTTP framework with typed request/response handlers
|
||||
|
||||
## Example Language Notes
|
||||
|
||||
> Uses generic type parameter `T extends BaseEntity` to ensure type safety across
|
||||
> repository methods. The constraint guarantees all entities share a common `id` field
|
||||
> while allowing specific entity types to flow through the data layer without casting.
|
||||
>
|
||||
> Barrel files (`index.ts`) re-export symbols so consumers import from the directory
|
||||
> rather than reaching into internal module paths — maintaining encapsulation.
|
||||
35
.qoder/skills/understand/languages/yaml.md
Normal file
35
.qoder/skills/understand/languages/yaml.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# YAML Language Prompt Snippet
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **Indentation-Based Nesting**: Whitespace-sensitive structure (spaces only, no tabs) defining hierarchy
|
||||
- **Anchors and Aliases**: `&anchor` defines a reusable block, `*anchor` references it to avoid duplication
|
||||
- **Merge Keys**: `<<: *anchor` merges anchor contents into the current mapping
|
||||
- **Multi-Line Strings**: Literal block (`|`) preserves newlines, folded block (`>`) joins lines
|
||||
- **Document Separators**: `---` starts a new document, `...` ends one (multi-document streams)
|
||||
- **Tags and Types**: `!!str`, `!!int`, `!!bool` for explicit typing; custom tags for application-specific types
|
||||
- **Flow Style**: Inline JSON-like syntax `{key: value}` and `[item1, item2]` for compact notation
|
||||
- **Environment Variable Substitution**: `${VAR}` patterns used in docker-compose and CI configs
|
||||
|
||||
## Notable File Patterns
|
||||
|
||||
- `docker-compose.yml` / `docker-compose.yaml` — Multi-container Docker application definition
|
||||
- `.github/workflows/*.yml` — GitHub Actions CI/CD workflow definitions
|
||||
- `.gitlab-ci.yml` — GitLab CI/CD pipeline configuration
|
||||
- `kubernetes/*.yaml` / `k8s/*.yaml` — Kubernetes resource manifests
|
||||
- `*.config.yaml` — Application configuration files
|
||||
- `mkdocs.yml` — MkDocs documentation site configuration
|
||||
- `serverless.yml` — Serverless Framework configuration
|
||||
|
||||
## Edge Patterns
|
||||
|
||||
- YAML config files `configures` the code modules they control (e.g., database settings affect data layer)
|
||||
- CI/CD YAML files `triggers` build and deployment pipelines
|
||||
- docker-compose YAML `deploys` services and `depends_on` Dockerfiles
|
||||
- Kubernetes YAML `deploys` and `provisions` application services
|
||||
|
||||
## Summary Style
|
||||
|
||||
> "Docker Compose configuration defining N services with networking, volumes, and health checks."
|
||||
> "GitHub Actions workflow running tests on push and deploying to production on merge to main."
|
||||
> "Kubernetes deployment manifest with N replicas, resource limits, and liveness probes."
|
||||
44
.qoder/skills/understand/locales/en.md
Normal file
44
.qoder/skills/understand/locales/en.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# English Output Guidelines
|
||||
|
||||
This file provides language-specific guidance for generating knowledge graph content in English.
|
||||
|
||||
## Tag Conventions
|
||||
|
||||
Use lowercase, hyphenated tags in English:
|
||||
|
||||
| Pattern | Recommended Tags |
|
||||
|---------|-----------------|
|
||||
| Entry point file | `entry-point`, `barrel`, `exports` |
|
||||
| Utility functions | `utility`, `helpers`, `common` |
|
||||
| API handlers | `api-handler`, `controller`, `endpoint` |
|
||||
| Data models | `data-model`, `entity`, `schema` |
|
||||
| Test files | `test`, `spec`, `unit-test` |
|
||||
| Configuration | `configuration`, `build-system`, `settings` |
|
||||
| Infrastructure | `infrastructure`, `deployment`, `containerization` |
|
||||
| Documentation | `documentation`, `guide`, `reference` |
|
||||
|
||||
## Summary Style
|
||||
|
||||
Write 1-2 sentence summaries that:
|
||||
- Describe **purpose** and **role** in the project
|
||||
- Use active voice ("Provides...", "Handles...", "Manages...")
|
||||
- Avoid restating the filename
|
||||
|
||||
**Examples:**
|
||||
- Good: "Provides date formatting and string sanitization helpers used across the API layer."
|
||||
- Bad: "The utils file contains utility functions."
|
||||
|
||||
## Technical Terms
|
||||
|
||||
Keep these terms in English (no translation needed):
|
||||
- `middleware`, `hook`, `barrel`, `entry-point`
|
||||
- `ORM`, `REST API`, `CI/CD`, `CRUD`
|
||||
- `singleton`, `factory`, `observer`
|
||||
- `middleware`, `interceptor`, `guard`
|
||||
|
||||
## Layer Names
|
||||
|
||||
Use standard English layer names:
|
||||
- `API Layer`, `Service Layer`, `Data Layer`, `UI Layer`
|
||||
- `Infrastructure`, `Configuration`, `Documentation`
|
||||
- `Utility Layer`, `Middleware Layer`, `Test Layer`
|
||||
49
.qoder/skills/understand/locales/ja.md
Normal file
49
.qoder/skills/understand/locales/ja.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 日本語出力ガイドライン (Japanese)
|
||||
|
||||
本ファイルは、日本語でナレッジグラフコンテンツを生成する際の言語固有のガイドラインを提供します。
|
||||
|
||||
## タグの命名規則
|
||||
|
||||
日本語タグまたは英語の一般的な技術用語を使用:
|
||||
|
||||
| パターン | 推奨タグ |
|
||||
|---------|---------|
|
||||
| エントリーポイント | `入口点`, `barrel`, `exports` または `entry-point` |
|
||||
| ユーティリティ | `ユーティリティ`, `helpers`, `utility` |
|
||||
| APIハンドラー | `api-handler`, `controller`, `endpoint` |
|
||||
| データモデル | `データモデル`, `entity`, `schema` または `data-model` |
|
||||
| テストファイル | `テスト`, `unit-test`, `test` |
|
||||
| 設定ファイル | `設定`, `build-system`, `configuration` |
|
||||
| インフラ | `インフラ`, `deployment`, `infrastructure` |
|
||||
| ドキュメント | `ドキュメント`, `guide`, `documentation` |
|
||||
|
||||
**混合戦略:** 一般的な技術用語は英語を保持(`middleware`, `api-handler`など)、説明用タグは日本語を使用可能。
|
||||
|
||||
## サマリーのスタイル
|
||||
|
||||
1-2文のサマリーを日本語で記述:
|
||||
- ファイルの**目的**と**役割**を説明
|
||||
- 能動態を使用(「提供する...」「処理する...」「管理する...」)
|
||||
- ファイル名の繰り返しを避ける
|
||||
|
||||
**例:**
|
||||
- 良い: "API層全体で使用される日付フォーマットと文字列サニタイズのヘルパー関数を提供。"
|
||||
- 悪い: "utilsファイルにはユーティリティ関数が含まれています。"
|
||||
|
||||
## 技術用語
|
||||
|
||||
以下の用語は英語を保持(標準翻訳がない場合):
|
||||
- `middleware`, `hook`, `barrel`, `entry-point`
|
||||
- `ORM`, `REST API`, `CI/CD`, `CRUD`
|
||||
- `singleton`, `factory`, `observer`
|
||||
- `interceptor`, `guard`
|
||||
|
||||
## レイヤー名
|
||||
|
||||
日本語のレイヤー名を使用:
|
||||
- `API層`, `サービス層`, `データ層`, `UI層`
|
||||
- `インフラ`, `設定`, `ドキュメント`
|
||||
- `ユーティリティ層`, `ミドルウェア層`, `テスト層`
|
||||
|
||||
または英語を保持(チームの慣習に従う):
|
||||
- `API Layer`, `Service Layer`, `Data Layer`
|
||||
49
.qoder/skills/understand/locales/ko.md
Normal file
49
.qoder/skills/understand/locales/ko.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 한국어 출력 가이드라인 (Korean)
|
||||
|
||||
이 파일은 한국어로 지식 그래프 콘텐츠를 생성할 때의 언어별 가이드를 제공합니다.
|
||||
|
||||
## 태그 명명 규칙
|
||||
|
||||
한국어 태그 또는 영어 일반 기술 용어 사용:
|
||||
|
||||
| 패턴 | 추천 태그 |
|
||||
|------|---------|
|
||||
| 진입점 파일 | `진입점`, `barrel`, `exports` 또는 `entry-point` |
|
||||
| 유틸리티 함수 | `유틸리티`, `helpers`, `utility` |
|
||||
| API 핸들러 | `api-handler`, `controller`, `endpoint` |
|
||||
| 데이터 모델 | `데이터모델`, `entity`, `schema` 또는 `data-model` |
|
||||
| 테스트 파일 | `테스트`, `unit-test`, `test` |
|
||||
| 설정 파일 | `설정`, `build-system`, `configuration` |
|
||||
| 인프라 | `인프라`, `deployment`, `infrastructure` |
|
||||
| 문서 | `문서`, `guide`, `documentation` |
|
||||
|
||||
**혼합 전략:** 일반 기술 용어는 영어 유지 (`middleware`, `api-handler` 등), 설명용 태그는 한국어 사용 가능.
|
||||
|
||||
## 요약 스타일
|
||||
|
||||
1-2문장 요약을 한국어로 작성:
|
||||
- 파일의 **목적**과 **역할** 설명
|
||||
- 능동태 사용 ("제공하는...", "처리하는...", "관리하는...")
|
||||
- 파일명 반복 피하기
|
||||
|
||||
**예시:**
|
||||
- 좋음: "API 레이어 전체에서 사용되는 날짜 포맷 및 문자열 정제 헬per 함수를 제공."
|
||||
- 나쁨: "utils 파일에는 유틸리티 함수가 포함되어 있습니다."
|
||||
|
||||
## 기술 용어
|
||||
|
||||
다음 용어는 영어 유지 (표준 번역 없음):
|
||||
- `middleware`, `hook`, `barrel`, `entry-point`
|
||||
- `ORM`, `REST API`, `CI/CD`, `CRUD`
|
||||
- `singleton`, `factory`, `observer`
|
||||
- `interceptor`, `guard`
|
||||
|
||||
## 레이어 이름
|
||||
|
||||
한국어 레이어 이름 사용:
|
||||
- `API 레이어`, `서비스 레이어`, `데이터 레이어`, `UI 레이어`
|
||||
- `인프라`, `설정`, `문서`
|
||||
- `유틸리티 레이어`, `미들웨어 레이어`, `테스트 레이어`
|
||||
|
||||
또는 영어 유지 (팀 관습에 따라):
|
||||
- `API Layer`, `Service Layer`, `Data Layer`
|
||||
49
.qoder/skills/understand/locales/ru.md
Normal file
49
.qoder/skills/understand/locales/ru.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Руководство по выводу на русском языке (Russian)
|
||||
|
||||
Этот файл содержит языковые рекомендации для генерации контента графа знаний на русском языке.
|
||||
|
||||
## Соглашения по тегам
|
||||
|
||||
Используйте русские теги или общепринятые английские технические термины:
|
||||
|
||||
| Шаблон | Рекомендуемые теги |
|
||||
|--------|--------------------|
|
||||
| Точка входа | `точка-входа`, `barrel`, `exports` или `entry-point` |
|
||||
| Утилитарные функции | `утилиты`, `helpers`, `common` или `utility` |
|
||||
| API-обработчики | `api-handler`, `контроллер`, `endpoint` |
|
||||
| Модели данных | `модель-данных`, `entity`, `schema` или `data-model` |
|
||||
| Тестовые файлы | `тесты`, `unit-test`, `test` |
|
||||
| Конфигурационные файлы | `конфигурация`, `build-system`, `settings` или `configuration` |
|
||||
| Инфраструктура | `инфраструктура`, `deployment`, `контейнеризация` или `infrastructure` |
|
||||
| Документация | `документация`, `руководство`, `documentation` |
|
||||
|
||||
**Смешанная стратегия:** общепринятые технические термины оставляйте на английском (например, `middleware`, `api-handler`), описательные теги можно писать на русском.
|
||||
|
||||
## Стиль резюме
|
||||
|
||||
Пишите резюме на русском в 1–2 предложениях:
|
||||
- Описывайте **назначение** и **роль** файла
|
||||
- Используйте активный залог («предоставляет…», «обрабатывает…», «управляет…»)
|
||||
- Избегайте повторения имени файла
|
||||
|
||||
**Примеры:**
|
||||
- Хорошо: «Предоставляет вспомогательные функции для форматирования дат и очистки строк, широко используемые на API-слое.»
|
||||
- Плохо: «Файл utils содержит утилитарные функции.»
|
||||
|
||||
## Технические термины
|
||||
|
||||
Следующие термины рекомендуется оставлять на английском (устоявшегося перевода нет):
|
||||
- `middleware`, `hook`, `barrel`, `entry-point`
|
||||
- `ORM`, `REST API`, `CI/CD`, `CRUD`
|
||||
- `singleton`, `factory`, `observer`
|
||||
- `interceptor`, `guard`
|
||||
|
||||
## Имена слоёв
|
||||
|
||||
Используйте русские имена слоёв:
|
||||
- `API-слой`, `сервисный слой`, `слой данных`, `UI-слой`
|
||||
- `инфраструктура`, `конфигурация`, `документация`
|
||||
- `утилитарный слой`, `слой middleware`, `тестовый слой`
|
||||
|
||||
Или оставляйте английские (по договорённости команды):
|
||||
- `API Layer`, `Service Layer`, `Data Layer`
|
||||
49
.qoder/skills/understand/locales/zh-TW.md
Normal file
49
.qoder/skills/understand/locales/zh-TW.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 繁體中文輸出指南 (Chinese Traditional)
|
||||
|
||||
本文件提供生成繁體中文知識圖譜內容的語言指導。
|
||||
|
||||
## 標籤約定
|
||||
|
||||
推薦使用繁體中文標籤或英文通用技術術語:
|
||||
|
||||
| 模式 | 推薦標籤 |
|
||||
|------|---------|
|
||||
| 入口檔案 | `入口點`, `barrel`, `匯出` 或 `entry-point` |
|
||||
| 工具函數 | `工具函數`, `helpers`, `common` 或 `utility` |
|
||||
| API處理器 | `api-handler`, `控制器`, `端點` |
|
||||
| 資料模型 | `資料模型`, `entity`, `schema` 或 `data-model` |
|
||||
| 測試檔案 | `測試`, `單元測試`, `test` |
|
||||
| 設定檔 | `設定`, `建構系統`, `settings` 或 `configuration` |
|
||||
| 基礎架構 | `基礎架構`, `部署`, `容器化` 或 `infrastructure` |
|
||||
| 文件 | `文件`, `指南`, `參考` 或 `documentation` |
|
||||
|
||||
**混合策略:** 通用技術術語保留英文(如 `middleware`, `api-handler`),描述性標籤可使用繁體中文。
|
||||
|
||||
## 摘要風格
|
||||
|
||||
用繁體中文撰寫1-2句摘要:
|
||||
- 描述檔案的**目的**和**作用**
|
||||
- 使用主動語態("提供...", "處理...", "管理...")
|
||||
- 避免重複檔名
|
||||
|
||||
**範例:**
|
||||
- 好: "提供日期格式化和字串清洗工具函數,被 API 層廣泛使用。"
|
||||
- 差: "utils 檔案包含工具函數。"
|
||||
|
||||
## 技術術語
|
||||
|
||||
以下術語建議保留英文(暫無標準翻譯):
|
||||
- `middleware`, `hook`, `barrel`, `entry-point`
|
||||
- `ORM`, `REST API`, `CI/CD`, `CRUD`
|
||||
- `singleton`, `factory`, `observer`
|
||||
- `interceptor`, `guard`
|
||||
|
||||
## 層級名稱
|
||||
|
||||
使用繁體中文層級名稱:
|
||||
- `API 層`, `服務層`, `資料層`, `UI 層`
|
||||
- `基礎架構`, `設定`, `文件`
|
||||
- `工具層`, `中介軟體層`, `測試層`
|
||||
|
||||
或保留英文(根據團隊習慣):
|
||||
- `API Layer`, `Service Layer`, `Data Layer`
|
||||
49
.qoder/skills/understand/locales/zh.md
Normal file
49
.qoder/skills/understand/locales/zh.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 中文输出指南 (Chinese Simplified)
|
||||
|
||||
本文件提供生成中文知识图谱内容的语言指导。
|
||||
|
||||
## 标签约定
|
||||
|
||||
推荐使用中文标签或英文通用技术术语:
|
||||
|
||||
| 模式 | 推荐标签 |
|
||||
|------|---------|
|
||||
| 入口文件 | `入口点`, `barrel`, `导出` 或 `entry-point` |
|
||||
| 工具函数 | `工具函数`, `helpers`, `common` 或 `utility` |
|
||||
| API处理器 | `api-handler`, `控制器`, `端点` |
|
||||
| 数据模型 | `数据模型`, `entity`, `schema` 或 `data-model` |
|
||||
| 测试文件 | `测试`, `单元测试`, `test` |
|
||||
| 配置文件 | `配置`, `构建系统`, `settings` 或 `configuration` |
|
||||
| 基础设施 | `基础设施`, `部署`, `容器化` 或 `infrastructure` |
|
||||
| 文档 | `文档`, `指南`, `参考` 或 `documentation` |
|
||||
|
||||
**混合策略:** 通用技术术语保留英文(如 `middleware`, `api-handler`),描述性标签可使用中文。
|
||||
|
||||
## 摘要风格
|
||||
|
||||
用中文撰写1-2句摘要:
|
||||
- 描述文件的**目的**和**作用**
|
||||
- 使用主动语态("提供...", "处理...", "管理...")
|
||||
- 避免重复文件名
|
||||
|
||||
**示例:**
|
||||
- 好: "提供日期格式化和字符串清洗工具函数,被 API 层广泛使用。"
|
||||
- 差: "utils 文件包含工具函数。"
|
||||
|
||||
## 技术术语
|
||||
|
||||
以下术语建议保留英文(暂无标准翻译):
|
||||
- `middleware`, `hook`, `barrel`, `entry-point`
|
||||
- `ORM`, `REST API`, `CI/CD`, `CRUD`
|
||||
- `singleton`, `factory`, `observer`
|
||||
- `interceptor`, `guard`
|
||||
|
||||
## 层级名称
|
||||
|
||||
使用中文层级名称:
|
||||
- `API 层`, `服务层`, `数据层`, `UI 层`
|
||||
- `基础设施`, `配置`, `文档`
|
||||
- `工具层`, `中间件层`, `测试层`
|
||||
|
||||
或保留英文(根据团队习惯):
|
||||
- `API Layer`, `Service Layer`, `Data Layer`
|
||||
1164
.qoder/skills/understand/merge-batch-graphs.py
Normal file
1164
.qoder/skills/understand/merge-batch-graphs.py
Normal file
File diff suppressed because it is too large
Load Diff
308
.qoder/skills/understand/merge-subdomain-graphs.py
Normal file
308
.qoder/skills/understand/merge-subdomain-graphs.py
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
merge-subdomain-graphs.py — Merge subdomain knowledge-graph files into one.
|
||||
|
||||
Auto-discovers *knowledge-graph*.json files in .understand-anything/
|
||||
(excluding knowledge-graph.json itself), loads the existing
|
||||
knowledge-graph.json as a base if present, and merges everything
|
||||
into a single knowledge-graph.json.
|
||||
|
||||
Usage:
|
||||
python merge-subdomain-graphs.py <project-root> [file1.json file2.json ...]
|
||||
|
||||
If no files are specified, auto-discovers subdomain graphs. The main
|
||||
knowledge-graph.json is loaded as a base but never as a discovery input
|
||||
(prevents self-merging on repeated runs).
|
||||
|
||||
Output:
|
||||
<project-root>/.understand-anything/knowledge-graph.json
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _num(v: Any) -> float:
|
||||
"""Coerce a value to float for safe comparison (handles string weights)."""
|
||||
try:
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return 0.0
|
||||
|
||||
|
||||
def load_graph(path: Path) -> dict[str, Any] | None:
|
||||
"""Load and minimally validate a knowledge graph JSON file."""
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as e:
|
||||
print(f" Skipping {path.name}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Must have at minimum nodes and edges arrays
|
||||
if not isinstance(data.get("nodes"), list) or not isinstance(data.get("edges"), list):
|
||||
print(f" Skipping {path.name}: missing nodes or edges array", file=sys.stderr)
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def merge_graphs(graphs: list[dict[str, Any]]) -> tuple[dict[str, Any], list[str]]:
|
||||
"""Merge multiple knowledge graph dicts into one. Returns (merged, report_lines)."""
|
||||
|
||||
# ── Pattern counters for "Fixed" report ──────────────────────────
|
||||
node_dedup_by_type: Counter[str] = Counter()
|
||||
|
||||
# ── Detail lists for "Could not fix" report ──────────────────────
|
||||
unfixable: list[str] = []
|
||||
|
||||
total_input_nodes = sum(len(g.get("nodes", [])) for g in graphs)
|
||||
total_input_edges = sum(len(g.get("edges", [])) for g in graphs)
|
||||
|
||||
# ── Nodes: deduplicate by id, later occurrence wins ───────────────
|
||||
nodes_by_id: dict[str, dict] = {}
|
||||
for g in graphs:
|
||||
for node in g.get("nodes", []):
|
||||
nid = node.get("id")
|
||||
if not nid:
|
||||
unfixable.append(f"Node with no 'id' (name={node.get('name', '?')}, type={node.get('type', '?')})")
|
||||
continue
|
||||
if nid in nodes_by_id:
|
||||
node_type = node.get("type", "?")
|
||||
node_dedup_by_type[node_type] += 1
|
||||
nodes_by_id[nid] = node
|
||||
|
||||
# ── Edges: deduplicate by (source, target, type), higher weight wins
|
||||
edge_dedup_count = 0
|
||||
edges_by_key: dict[tuple[str, str, str], dict] = {}
|
||||
for g in graphs:
|
||||
for edge in g.get("edges", []):
|
||||
key = (edge.get("source", ""), edge.get("target", ""), edge.get("type", ""))
|
||||
existing = edges_by_key.get(key)
|
||||
if existing is None:
|
||||
edges_by_key[key] = edge
|
||||
else:
|
||||
edge_dedup_count += 1
|
||||
if _num(edge.get("weight", 0)) > _num(existing.get("weight", 0)):
|
||||
edges_by_key[key] = edge
|
||||
|
||||
# Drop edges referencing missing nodes
|
||||
node_ids = set(nodes_by_id.keys())
|
||||
valid_edges: list[dict] = []
|
||||
for e in edges_by_key.values():
|
||||
src, tgt = e.get("source", ""), e.get("target", "")
|
||||
if src in node_ids and tgt in node_ids:
|
||||
valid_edges.append(e)
|
||||
else:
|
||||
missing = []
|
||||
if src not in node_ids:
|
||||
missing.append(f"source '{src}'")
|
||||
if tgt not in node_ids:
|
||||
missing.append(f"target '{tgt}'")
|
||||
unfixable.append(f"Edge {src} → {tgt} ({e.get('type', '?')}): dropped, missing {', '.join(missing)}")
|
||||
|
||||
# ── Layers: merge by id, union nodeIds ────────────────────────────
|
||||
layers_by_id: dict[str, dict] = {}
|
||||
for g in graphs:
|
||||
for layer in g.get("layers", []):
|
||||
lid = layer.get("id", "")
|
||||
if lid in layers_by_id:
|
||||
existing_ids = set(layers_by_id[lid].get("nodeIds", []))
|
||||
existing_ids.update(layer.get("nodeIds", []))
|
||||
layers_by_id[lid]["nodeIds"] = list(existing_ids)
|
||||
else:
|
||||
layers_by_id[lid] = {**layer}
|
||||
|
||||
# Drop dangling layer nodeIds
|
||||
dropped_layer_refs = 0
|
||||
for layer in layers_by_id.values():
|
||||
before = len(layer.get("nodeIds", []))
|
||||
layer["nodeIds"] = [nid for nid in layer.get("nodeIds", []) if nid in node_ids]
|
||||
diff = before - len(layer["nodeIds"])
|
||||
if diff:
|
||||
dropped_layer_refs += diff
|
||||
|
||||
# ── Tour: concatenate, merge steps with same title ─────────────────
|
||||
all_tour_steps: list[dict] = []
|
||||
title_to_step: dict[str, dict] = {}
|
||||
for g in graphs:
|
||||
for step in g.get("tour", []):
|
||||
title = step.get("title", "")
|
||||
if title in title_to_step:
|
||||
# Merge nodeIds from duplicate-titled steps (e.g. both
|
||||
# subdomains produce a "Project Overview" step 1)
|
||||
existing = title_to_step[title]
|
||||
for nid in step.get("nodeIds", []):
|
||||
if nid not in existing.get("nodeIds", []):
|
||||
existing.setdefault("nodeIds", []).append(nid)
|
||||
# Keep the longer description
|
||||
if len(step.get("description", "")) > len(existing.get("description", "")):
|
||||
existing["description"] = step["description"]
|
||||
else:
|
||||
new_step = {**step}
|
||||
title_to_step[title] = new_step
|
||||
all_tour_steps.append(new_step)
|
||||
|
||||
# Drop dangling tour nodeIds and re-number
|
||||
dropped_tour_refs = 0
|
||||
for i, step in enumerate(all_tour_steps, start=1):
|
||||
step["order"] = i
|
||||
before = len(step.get("nodeIds", []))
|
||||
step["nodeIds"] = [nid for nid in step.get("nodeIds", []) if nid in node_ids]
|
||||
diff = before - len(step["nodeIds"])
|
||||
if diff:
|
||||
dropped_tour_refs += diff
|
||||
|
||||
# ── Project metadata: merge ───────────────────────────────────────
|
||||
languages: list[str] = []
|
||||
frameworks: list[str] = []
|
||||
descriptions: list[str] = []
|
||||
latest_at = ""
|
||||
latest_hash = ""
|
||||
project_name = ""
|
||||
|
||||
for g in graphs:
|
||||
proj = g.get("project", {})
|
||||
project_name = proj.get("name", "") or project_name
|
||||
for lang in proj.get("languages", []):
|
||||
if lang not in languages:
|
||||
languages.append(lang)
|
||||
for fw in proj.get("frameworks", []):
|
||||
if fw not in frameworks:
|
||||
frameworks.append(fw)
|
||||
desc = proj.get("description", "")
|
||||
if desc and desc not in descriptions:
|
||||
descriptions.append(desc)
|
||||
analyzed = proj.get("analyzedAt", "")
|
||||
if analyzed > latest_at:
|
||||
latest_at = analyzed
|
||||
latest_hash = proj.get("gitCommitHash", latest_hash)
|
||||
|
||||
# ── Build report ─────────────────────────────────────────────────
|
||||
report: list[str] = []
|
||||
report.append(f"Input: {total_input_nodes} nodes, {total_input_edges} edges (from {len(graphs)} graphs)")
|
||||
|
||||
# Fixed section
|
||||
fixed_lines: list[str] = []
|
||||
if node_dedup_by_type:
|
||||
for ntype, count in node_dedup_by_type.most_common():
|
||||
fixed_lines.append(f" {count:>4} × duplicate '{ntype}' nodes removed (kept later)")
|
||||
if edge_dedup_count:
|
||||
fixed_lines.append(f" {edge_dedup_count:>4} × duplicate edges removed (kept higher weight)")
|
||||
if dropped_layer_refs:
|
||||
fixed_lines.append(f" {dropped_layer_refs:>4} × dangling layer nodeId refs removed")
|
||||
if dropped_tour_refs:
|
||||
fixed_lines.append(f" {dropped_tour_refs:>4} × dangling tour nodeId refs removed")
|
||||
|
||||
if fixed_lines:
|
||||
total_fixed = sum(node_dedup_by_type.values()) + edge_dedup_count + dropped_layer_refs + dropped_tour_refs
|
||||
report.append("")
|
||||
report.append(f"Fixed ({total_fixed} corrections):")
|
||||
report.extend(fixed_lines)
|
||||
|
||||
# Could not fix section
|
||||
if unfixable:
|
||||
report.append("")
|
||||
report.append(f"Could not fix ({len(unfixable)} issues — needs agent review):")
|
||||
for detail in unfixable:
|
||||
report.append(f" - {detail}")
|
||||
|
||||
# Output stats
|
||||
report.append("")
|
||||
report.append(f"Output: {len(nodes_by_id)} nodes, {len(valid_edges)} edges, {len(layers_by_id)} layers, {len(all_tour_steps)} tour steps")
|
||||
|
||||
merged: dict[str, Any] = {
|
||||
"version": "1.0.0",
|
||||
"project": {
|
||||
"name": project_name,
|
||||
"languages": languages,
|
||||
"frameworks": frameworks,
|
||||
"description": " | ".join(descriptions) if len(descriptions) > 1 else (descriptions[0] if descriptions else ""),
|
||||
"analyzedAt": latest_at,
|
||||
"gitCommitHash": latest_hash,
|
||||
},
|
||||
"nodes": list(nodes_by_id.values()),
|
||||
"edges": valid_edges,
|
||||
"layers": list(layers_by_id.values()),
|
||||
"tour": all_tour_steps,
|
||||
}
|
||||
|
||||
return merged, report
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python merge-subdomain-graphs.py <project-root> [file1.json file2.json ...]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
project_root = Path(sys.argv[1]).resolve()
|
||||
ua_dir = project_root / ".understand-anything"
|
||||
|
||||
if not ua_dir.is_dir():
|
||||
print(f"Error: {ua_dir} does not exist", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
output_path = ua_dir / "knowledge-graph.json"
|
||||
|
||||
# Determine which files to merge
|
||||
if len(sys.argv) > 2:
|
||||
# Explicit file list
|
||||
graph_files = [Path(f).resolve() for f in sys.argv[2:]]
|
||||
else:
|
||||
# Auto-discover subdomain graphs — exclude the main output file
|
||||
# to avoid self-merging on repeated runs
|
||||
graph_files = sorted(
|
||||
p for p in ua_dir.glob("*knowledge-graph*.json")
|
||||
if p.name != "knowledge-graph.json"
|
||||
)
|
||||
|
||||
if not graph_files:
|
||||
print("No subdomain graphs found to merge", file=sys.stderr)
|
||||
sys.exit(0)
|
||||
|
||||
print(f"Found {len(graph_files)} subdomain graphs:", file=sys.stderr)
|
||||
for f in graph_files:
|
||||
print(f" - {f.name}", file=sys.stderr)
|
||||
|
||||
# Load subdomain graphs
|
||||
graphs: list[dict[str, Any]] = []
|
||||
for f in graph_files:
|
||||
g = load_graph(f)
|
||||
if g is not None:
|
||||
graphs.append(g)
|
||||
node_count = len(g.get("nodes", []))
|
||||
edge_count = len(g.get("edges", []))
|
||||
print(f" Loaded {f.name}: {node_count} nodes, {edge_count} edges", file=sys.stderr)
|
||||
|
||||
if not graphs:
|
||||
print("Error: no valid subdomain graphs loaded", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Load the existing main graph as base (if it exists)
|
||||
if output_path.exists():
|
||||
base = load_graph(output_path)
|
||||
if base:
|
||||
node_count = len(base.get("nodes", []))
|
||||
edge_count = len(base.get("edges", []))
|
||||
print(f" Loaded base knowledge-graph.json: {node_count} nodes, {edge_count} edges", file=sys.stderr)
|
||||
graphs.insert(0, base) # Base first — subdomain data wins on conflict
|
||||
|
||||
# Merge
|
||||
merged, report = merge_graphs(graphs)
|
||||
|
||||
# Print report
|
||||
print("", file=sys.stderr)
|
||||
for line in report:
|
||||
print(line, file=sys.stderr)
|
||||
|
||||
# Write output
|
||||
output_path.write_text(json.dumps(merged, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
size_kb = output_path.stat().st_size / 1024
|
||||
print(f"\nWritten to {output_path} ({size_kb:.0f} KB)", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
802
.qoder/skills/understand/scan-project.mjs
Normal file
802
.qoder/skills/understand/scan-project.mjs
Normal file
@@ -0,0 +1,802 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* scan-project.mjs
|
||||
*
|
||||
* Deterministic file enumeration + language/category detection for the
|
||||
* project-scanner agent. Replaces the LLM-written prose scanner that used to
|
||||
* (a) author a per-run Node.js script (`tmp/ua-project-scan.js`), (b) walk the
|
||||
* file tree, and (c) classify each file via lookup tables in LLM context — a
|
||||
* pure rule-lookup pass that was being billed at LLM rates and adding many
|
||||
* minutes of per-run latency on mid-sized monorepos.
|
||||
*
|
||||
* What the LLM still owns (Step A of project-scanner.md Phase 1):
|
||||
* - Reading README + top-level manifests to synthesize `name`,
|
||||
* `rawDescription`, `readmeHead`, `frameworks`, and the high-level
|
||||
* `languages` narrative.
|
||||
*
|
||||
* What this script owns:
|
||||
* - File enumeration (git ls-files preferred, recursive walk fallback)
|
||||
* - `.understandignore` filtering (delegated to core's createIgnoreFilter)
|
||||
* - Per-file language detection (extension + filename table)
|
||||
* - Per-file category assignment (priority-ordered rules from
|
||||
* project-scanner.md Step 4)
|
||||
* - Line counting
|
||||
* - Complexity estimation (project-scanner.md Step 7 thresholds)
|
||||
*
|
||||
* Usage:
|
||||
* node scan-project.mjs <projectRoot> <outputPath>
|
||||
*
|
||||
* Output JSON (subset of what project-scanner.md Phase 1 expects — the LLM
|
||||
* agent merges this with Step A's narrative fields and Step C's importMap to
|
||||
* produce the final scan-result.json):
|
||||
* {
|
||||
* "scriptCompleted": true,
|
||||
* "files": [{ "path": "...", "language": "...", "sizeLines": N, "fileCategory": "..." }, ...],
|
||||
* "totalFiles": N,
|
||||
* "filteredByIgnore": M,
|
||||
* "estimatedComplexity": "small" | "moderate" | "large" | "very-large",
|
||||
* "stats": { "filesScanned": N, "byCategory": {...}, "byLanguage": {...} }
|
||||
* }
|
||||
*
|
||||
* Logging: stderr only (stdout reserved for piped tooling).
|
||||
* Per-file resilience: read/stat failures emit
|
||||
* `Warning: scan-project: <path> — <reason> — file skipped from output`
|
||||
* to stderr and the file is dropped; the rest of the scan completes.
|
||||
*
|
||||
* Determinism: files are sorted by `path.localeCompare` before emission, and
|
||||
* the underlying enumeration is deterministic (git ls-files returns a stable
|
||||
* order; the fallback walker sorts each directory's entries).
|
||||
*/
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, resolve, join, basename, extname, relative, sep } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import {
|
||||
existsSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
realpathSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
// skills/understand/ -> plugin root is two dirs up
|
||||
const pluginRoot = resolve(__dirname, '../..');
|
||||
const require = createRequire(resolve(pluginRoot, 'package.json'));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resolve @understand-anything/core
|
||||
//
|
||||
// Two-step resolution: try the workspace-linked package first, fall back to
|
||||
// the installed plugin cache layout. pathToFileURL() is required on Windows
|
||||
// because dynamic import() of raw "C:\..." paths throws
|
||||
// ERR_UNSUPPORTED_ESM_URL_SCHEME (Node parses "C:" as a URL scheme).
|
||||
// ---------------------------------------------------------------------------
|
||||
let core;
|
||||
try {
|
||||
core = await import(pathToFileURL(require.resolve('@understand-anything/core')).href);
|
||||
} catch {
|
||||
core = await import(pathToFileURL(resolve(pluginRoot, 'packages/core/dist/index.js')).href);
|
||||
}
|
||||
|
||||
const { createIgnoreFilter } = core;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Language detection
|
||||
//
|
||||
// Mirrors the canonical extension list from
|
||||
// understand-anything-plugin/packages/core/src/languages/configs/* and the
|
||||
// project-scanner.md Step 3 table. Extensions are matched lowercase;
|
||||
// filenames (Dockerfile, Makefile, etc.) are matched case-sensitively because
|
||||
// the projects-in-the-wild use canonical capitalizations.
|
||||
//
|
||||
// Where the core configs and project-scanner.md diverge (rare), project-
|
||||
// scanner.md wins because it is the user-facing contract.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Extension -> language id. Lowercase keys; lookup is `.ext.toLowerCase()`.
|
||||
* Includes the legacy Step-3 mapping (.cfg/.ini/.env -> `config`) — note
|
||||
* that `config` is a language id here, not a category. Category routing
|
||||
* for these extensions is handled separately in CATEGORY_BY_EXT.
|
||||
*/
|
||||
const LANGUAGE_BY_EXT = Object.freeze({
|
||||
// TypeScript / JavaScript
|
||||
'.ts': 'typescript',
|
||||
'.tsx': 'typescript',
|
||||
'.js': 'javascript',
|
||||
'.jsx': 'javascript',
|
||||
'.mjs': 'javascript',
|
||||
'.cjs': 'javascript',
|
||||
// Python
|
||||
'.py': 'python',
|
||||
'.pyi': 'python',
|
||||
// Go / Rust / Java / Kotlin / C# / Swift / Lua
|
||||
'.go': 'go',
|
||||
'.rs': 'rust',
|
||||
'.java': 'java',
|
||||
'.kt': 'kotlin',
|
||||
'.kts': 'kotlin',
|
||||
'.cs': 'csharp',
|
||||
'.swift': 'swift',
|
||||
'.lua': 'lua',
|
||||
// Ruby / PHP
|
||||
'.rb': 'ruby',
|
||||
'.rake': 'ruby',
|
||||
'.php': 'php',
|
||||
// C / C++
|
||||
'.c': 'c',
|
||||
'.h': 'c',
|
||||
'.cpp': 'cpp',
|
||||
'.cc': 'cpp',
|
||||
'.cxx': 'cpp',
|
||||
'.hpp': 'cpp',
|
||||
'.hxx': 'cpp',
|
||||
// Vue / Svelte (no tree-sitter extractor, but project-scanner contract
|
||||
// lists them as code languages — downstream import map will return [])
|
||||
'.vue': 'vue',
|
||||
'.svelte': 'svelte',
|
||||
// Shell / Batch / PowerShell
|
||||
'.sh': 'shell',
|
||||
'.bash': 'shell',
|
||||
'.zsh': 'shell',
|
||||
'.ps1': 'powershell',
|
||||
'.psm1': 'powershell',
|
||||
'.psd1': 'powershell',
|
||||
'.bat': 'batch',
|
||||
'.cmd': 'batch',
|
||||
// Markup / docs
|
||||
'.html': 'html',
|
||||
'.htm': 'html',
|
||||
'.css': 'css',
|
||||
'.scss': 'css',
|
||||
'.sass': 'css',
|
||||
'.less': 'css',
|
||||
'.md': 'markdown',
|
||||
'.mdx': 'markdown',
|
||||
'.rst': 'markdown',
|
||||
// Config / data
|
||||
'.yaml': 'yaml',
|
||||
'.yml': 'yaml',
|
||||
'.json': 'json',
|
||||
'.jsonc': 'jsonc',
|
||||
'.toml': 'toml',
|
||||
'.xml': 'xml',
|
||||
'.xsl': 'xml',
|
||||
'.xsd': 'xml',
|
||||
'.plist': 'xml',
|
||||
'.cfg': 'config',
|
||||
'.ini': 'config',
|
||||
'.env': 'config',
|
||||
// Data / schema
|
||||
'.sql': 'sql',
|
||||
'.graphql': 'graphql',
|
||||
'.gql': 'graphql',
|
||||
'.proto': 'protobuf',
|
||||
'.prisma': 'prisma',
|
||||
'.csv': 'csv',
|
||||
'.tsv': 'csv',
|
||||
// Infra
|
||||
'.tf': 'terraform',
|
||||
'.tfvars': 'terraform',
|
||||
// JVM build files (categorized via filename-or-extension)
|
||||
'.gradle': 'gradle',
|
||||
// .NET project files (mapped to extension-derived ids; downstream
|
||||
// treats them as config — see CATEGORY_BY_EXT)
|
||||
'.csproj': 'csproj',
|
||||
'.sln': 'sln',
|
||||
'.properties': 'properties',
|
||||
'.mod': 'mod',
|
||||
'.sum': 'sum',
|
||||
});
|
||||
|
||||
/**
|
||||
* Filename (no extension) -> language id. Compared case-sensitively against
|
||||
* basename(path). Includes the most common no-extension conventions; anything
|
||||
* NOT in this table with no extension falls back to `unknown`.
|
||||
*
|
||||
* Dockerfile.* variants (Dockerfile.dev, Dockerfile.prod) are handled by a
|
||||
* startsWith check in `detectLanguage()` so we don't have to enumerate every
|
||||
* possible suffix.
|
||||
*/
|
||||
const LANGUAGE_BY_FILENAME = Object.freeze({
|
||||
Dockerfile: 'dockerfile',
|
||||
Makefile: 'makefile',
|
||||
GNUmakefile: 'makefile',
|
||||
makefile: 'makefile',
|
||||
Jenkinsfile: 'jenkinsfile',
|
||||
Procfile: 'procfile',
|
||||
Vagrantfile: 'vagrantfile',
|
||||
});
|
||||
|
||||
/**
|
||||
* Detect the language of a file by its path. Lowercase extension lookup,
|
||||
* then no-extension filename lookup. Never returns null — falls back to
|
||||
* the lowercased extension (without dot) or 'unknown' if there is no
|
||||
* extension. Downstream consumers rely on this field always being a string
|
||||
* (see project-scanner.md Step 3 "Fallback" note).
|
||||
*/
|
||||
export function detectLanguage(filePath) {
|
||||
const base = basename(filePath);
|
||||
const ext = extname(filePath).toLowerCase();
|
||||
|
||||
// Dockerfile.dev, Dockerfile.prod, etc. — common variant form.
|
||||
if (base === 'Dockerfile' || base.startsWith('Dockerfile.')) return 'dockerfile';
|
||||
|
||||
// Dotfile names like .env, .env.local — path.extname returns '' for
|
||||
// single-segment dotfiles (e.g. '.env') and the SECOND segment for
|
||||
// compound dotfiles (e.g. '.local' for '.env.local'). Neither hits the
|
||||
// intended LANGUAGE_BY_EXT['.env'] mapping. Try the leading dotfile
|
||||
// portion first so `.env`, `.env.local`, `.env.production` all map.
|
||||
const dotKey = dotfileKey(base);
|
||||
if (dotKey && LANGUAGE_BY_EXT[dotKey]) return LANGUAGE_BY_EXT[dotKey];
|
||||
|
||||
if (ext) {
|
||||
const byExt = LANGUAGE_BY_EXT[ext];
|
||||
if (byExt) return byExt;
|
||||
// Unknown extension → drop the leading dot, lowercase. Never null.
|
||||
return ext.slice(1);
|
||||
}
|
||||
|
||||
// No-extension file — try filename table.
|
||||
const byFilename = LANGUAGE_BY_FILENAME[base];
|
||||
if (byFilename) return byFilename;
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the canonical dotfile "extension" from a basename, or null.
|
||||
*
|
||||
* `.env` -> `.env`
|
||||
* `.env.local` -> `.env`
|
||||
* `.bashrc` -> `.bashrc`
|
||||
* `package.json` -> null (not a dotfile)
|
||||
*
|
||||
* Used by both detectLanguage and detectCategory so dotfile-style configs
|
||||
* (e.g., `.env`, `.env.local`, `.env.production`) get their leading
|
||||
* segment treated as the implicit extension instead of falling through
|
||||
* to `unknown` / `code`.
|
||||
*/
|
||||
function dotfileKey(base) {
|
||||
if (!base.startsWith('.')) return null;
|
||||
const m = base.match(/^(\.[a-z0-9]+)/i);
|
||||
return m ? m[1].toLowerCase() : null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Category detection
|
||||
//
|
||||
// Implements the priority-ordered rules from project-scanner.md Step 4.
|
||||
// Order matters: more specific rules must run before more general ones
|
||||
// (e.g. `docker-compose.yml` is infra, not config).
|
||||
//
|
||||
// Categories: code | config | docs | infra | data | script | markup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Extension -> category. Used only after the higher-priority path-based
|
||||
* checks (infra/docs exclusions) in `detectCategory()`. Plain extension
|
||||
* lookup is intentionally last-resort — many configs need their full path
|
||||
* inspected first.
|
||||
*/
|
||||
const CATEGORY_BY_EXT = Object.freeze({
|
||||
// docs
|
||||
'.md': 'docs',
|
||||
'.mdx': 'docs',
|
||||
'.rst': 'docs',
|
||||
'.txt': 'docs',
|
||||
'.text': 'docs',
|
||||
// config
|
||||
'.yaml': 'config',
|
||||
'.yml': 'config',
|
||||
'.json': 'config',
|
||||
'.jsonc': 'config',
|
||||
'.toml': 'config',
|
||||
'.xml': 'config',
|
||||
'.xsl': 'config',
|
||||
'.xsd': 'config',
|
||||
'.plist': 'config',
|
||||
'.cfg': 'config',
|
||||
'.ini': 'config',
|
||||
'.env': 'config',
|
||||
'.properties': 'config',
|
||||
'.csproj': 'config',
|
||||
'.sln': 'config',
|
||||
'.mod': 'config',
|
||||
'.sum': 'config',
|
||||
'.gradle': 'config',
|
||||
// infra
|
||||
'.tf': 'infra',
|
||||
'.tfvars': 'infra',
|
||||
// data
|
||||
'.sql': 'data',
|
||||
'.graphql': 'data',
|
||||
'.gql': 'data',
|
||||
'.proto': 'data',
|
||||
'.prisma': 'data',
|
||||
'.csv': 'data',
|
||||
'.tsv': 'data',
|
||||
// script
|
||||
'.sh': 'script',
|
||||
'.bash': 'script',
|
||||
'.zsh': 'script',
|
||||
'.ps1': 'script',
|
||||
'.psm1': 'script',
|
||||
'.psd1': 'script',
|
||||
'.bat': 'script',
|
||||
'.cmd': 'script',
|
||||
// markup
|
||||
'.html': 'markup',
|
||||
'.htm': 'markup',
|
||||
'.css': 'markup',
|
||||
'.scss': 'markup',
|
||||
'.sass': 'markup',
|
||||
'.less': 'markup',
|
||||
});
|
||||
|
||||
/**
|
||||
* Filenames (no extension or full filename with extension) that always
|
||||
* map to `infra` regardless of their extension. Compared case-sensitively
|
||||
* against basename(path).
|
||||
*/
|
||||
const INFRA_FILENAMES = new Set([
|
||||
'Dockerfile',
|
||||
'.dockerignore',
|
||||
'Makefile',
|
||||
'GNUmakefile',
|
||||
'makefile',
|
||||
'Jenkinsfile',
|
||||
'Procfile',
|
||||
'Vagrantfile',
|
||||
'.gitlab-ci.yml',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Detect the project-scanner category for a file. Priority order matches
|
||||
* project-scanner.md Step 4 "Priority rule" — most specific wins.
|
||||
*
|
||||
* 1. LICENSE -> code (per the spec note "except LICENSE"). The Step-2
|
||||
* exclusion table normally removes LICENSE, but if a project chooses to
|
||||
* re-include it via `.understandignore` negation, it should NOT land in
|
||||
* docs. We classify as `code` rather than inventing a new bucket.
|
||||
* 2. Filename-based infra (Dockerfile, Makefile, Jenkinsfile,
|
||||
* docker-compose.*, Vagrantfile, Procfile, .gitlab-ci.yml,
|
||||
* .dockerignore).
|
||||
* 3. Path-based infra (.github/workflows/, .circleci/, k8s/, kubernetes/,
|
||||
* *.k8s.yml, *.k8s.yaml).
|
||||
* 4. Extension-based mapping (CATEGORY_BY_EXT).
|
||||
* 5. Fallback: `code` (matches the spec — "All other extensions").
|
||||
*/
|
||||
export function detectCategory(filePath) {
|
||||
const base = basename(filePath);
|
||||
const ext = extname(filePath).toLowerCase();
|
||||
const posix = filePath.split(sep).join('/');
|
||||
|
||||
// Rule 1: LICENSE exception (project-scanner.md Step 4 table comment).
|
||||
if (base === 'LICENSE') return 'code';
|
||||
|
||||
// Rule 2: infra by filename — Dockerfile + variants, Makefile,
|
||||
// Jenkinsfile, docker-compose.*, Procfile, Vagrantfile, .gitlab-ci.yml,
|
||||
// .dockerignore.
|
||||
if (INFRA_FILENAMES.has(base)) return 'infra';
|
||||
if (base === 'Dockerfile' || base.startsWith('Dockerfile.')) return 'infra';
|
||||
if (base.startsWith('docker-compose.')) return 'infra';
|
||||
if (base === 'compose.yml' || base === 'compose.yaml') return 'infra';
|
||||
|
||||
// Rule 3: infra by path.
|
||||
if (posix.startsWith('.github/workflows/')) return 'infra';
|
||||
if (posix.startsWith('.circleci/')) return 'infra';
|
||||
// Match a `k8s/` or `kubernetes/` segment anywhere in the path.
|
||||
if (/(^|\/)(k8s|kubernetes)\//.test(posix)) return 'infra';
|
||||
// `*.k8s.yml` and `*.k8s.yaml` — Kubernetes-flavored YAML.
|
||||
if (/\.k8s\.(ya?ml)$/i.test(base)) return 'infra';
|
||||
|
||||
// Rule 4: extension-based lookup.
|
||||
if (ext) {
|
||||
const byExt = CATEGORY_BY_EXT[ext];
|
||||
if (byExt) return byExt;
|
||||
}
|
||||
|
||||
// Rule 4.5: dotfile-style configs (.env, .env.local, .env.production).
|
||||
// path.extname misses these — see dotfileKey docstring.
|
||||
const dotKey = dotfileKey(base);
|
||||
if (dotKey) {
|
||||
const byDot = CATEGORY_BY_EXT[dotKey];
|
||||
if (byDot) return byDot;
|
||||
}
|
||||
|
||||
// Rule 5: filename-based config catch-all for no-extension config files
|
||||
// commonly seen in JVM/Go/.NET projects (covered above for infra but not
|
||||
// config). We don't enumerate every possible config filename here — that
|
||||
// gets handled by the language map's no-extension entries upstream.
|
||||
// Anything not matched falls through to `code`.
|
||||
return 'code';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complexity estimation (project-scanner.md Step 7)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Map a total file count to a complexity tier. Thresholds are inclusive on
|
||||
* the lower bound:
|
||||
* - small: 1-30
|
||||
* - moderate: 31-150
|
||||
* - large: 151-500
|
||||
* - very-large: >500
|
||||
*
|
||||
* Edge case: 0 files maps to `small` (the lowest tier) so the field is
|
||||
* always set even on empty repos. Downstream consumers treat 0 files as
|
||||
* a sentinel for "nothing to analyze" via `totalFiles`, not complexity.
|
||||
*/
|
||||
export function estimateComplexity(totalFiles) {
|
||||
if (totalFiles <= 30) return 'small';
|
||||
if (totalFiles <= 150) return 'moderate';
|
||||
if (totalFiles <= 500) return 'large';
|
||||
return 'very-large';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// File enumeration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalize a path to forward-slash POSIX. The project-scanner contract
|
||||
* emits POSIX paths; we re-normalize so the output is stable across
|
||||
* Windows/macOS/Linux.
|
||||
*/
|
||||
function toPosix(p) {
|
||||
return p.split(sep).join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all files in `projectRoot` via `git ls-files`. Returns an
|
||||
* array of project-relative POSIX paths, or null if the directory is not
|
||||
* a git repository (or git is not installed). Caller falls back to the
|
||||
* recursive walker.
|
||||
*
|
||||
* Why git ls-files first: it respects the repo's `.gitignore`, handles
|
||||
* submodules sensibly, and gives a fast, deterministic listing. The walker
|
||||
* is a strict superset of what git would emit (no .gitignore awareness),
|
||||
* so the ignore filter has to do more work in the fallback path.
|
||||
*/
|
||||
function enumerateViaGit(projectRoot) {
|
||||
// -z = NUL-terminated output. Without it, `git ls-files` C-escapes non-ASCII
|
||||
// bytes in path names — paths containing emoji, accented characters, CJK
|
||||
// codepoints, etc. come back quoted with octal escapes (e.g.
|
||||
// `"30. \360\237\217\227 BD-CCER/file.md"` for a path containing 🏗️).
|
||||
// Those quoted-escaped strings then fail to round-trip back to real disk
|
||||
// paths in downstream consumers, so files in such directories are silently
|
||||
// dropped from the scan. The -z form emits raw bytes between NUL separators,
|
||||
// preserving every codepoint as-is. This is the same approach git itself
|
||||
// uses for `--null` everywhere downstream (xargs -0, etc.).
|
||||
const result = spawnSync('git', ['ls-files', '-z', '-co', '--exclude-standard'], {
|
||||
cwd: projectRoot,
|
||||
encoding: 'utf-8',
|
||||
maxBuffer: 256 * 1024 * 1024, // 256MB — huge monorepos can produce >10MB of paths
|
||||
});
|
||||
if (result.status !== 0 || !result.stdout) return null;
|
||||
// Each NUL-separated chunk is one path, project-relative, already POSIX on
|
||||
// all platforms because git emits forward slashes regardless of OS.
|
||||
return result.stdout
|
||||
.split('\0')
|
||||
.filter(Boolean)
|
||||
.map(toPosix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive directory walker — fallback when `git ls-files` is unavailable
|
||||
* (no git, not a repo, or git refused). Skips hard-coded "obviously bad"
|
||||
* directory names BEFORE invoking the ignore filter so we don't waste cycles
|
||||
* descending into `node_modules/` etc. on huge trees.
|
||||
*
|
||||
* Yields project-relative POSIX paths in directory-sorted order so the
|
||||
* output is deterministic without an extra sort pass.
|
||||
*/
|
||||
function enumerateViaWalk(projectRoot) {
|
||||
// Hard skip — these directories are universally non-source and skipping
|
||||
// at the walker level avoids materializing thousands of node_modules
|
||||
// paths before the ignore filter drops them. The ignore filter still
|
||||
// runs on everything else.
|
||||
const HARD_SKIP_DIRS = new Set([
|
||||
'node_modules',
|
||||
'.git',
|
||||
'.svn',
|
||||
'.hg',
|
||||
'__pycache__',
|
||||
]);
|
||||
|
||||
const out = [];
|
||||
|
||||
function walk(absDir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = readdirSync(absDir, { withFileTypes: true });
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: scan-project: ${toPosix(relative(projectRoot, absDir)) || '.'} ` +
|
||||
`— directory read failed (${err.message}) — subtree skipped\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Sort deterministically by name; mix files and dirs together so the
|
||||
// final output (after the path sort) is identical regardless of
|
||||
// OS-specific readdir order.
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name));
|
||||
for (const ent of entries) {
|
||||
if (ent.isDirectory()) {
|
||||
if (HARD_SKIP_DIRS.has(ent.name)) continue;
|
||||
walk(join(absDir, ent.name));
|
||||
} else if (ent.isFile()) {
|
||||
const rel = toPosix(relative(projectRoot, join(absDir, ent.name)));
|
||||
if (rel) out.push(rel);
|
||||
}
|
||||
// Symlinks intentionally ignored — git ls-files doesn't follow them
|
||||
// either, and following them is a classic recursion-bomb footgun.
|
||||
}
|
||||
}
|
||||
|
||||
walk(projectRoot);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all candidate files in `projectRoot`. Tries git ls-files first;
|
||||
* falls back to a recursive walk if git is unavailable or this is not a
|
||||
* repo. Returns an array of project-relative POSIX paths in unspecified
|
||||
* order — caller is responsible for sorting + filtering.
|
||||
*/
|
||||
function enumerateFiles(projectRoot) {
|
||||
const fromGit = enumerateViaGit(projectRoot);
|
||||
if (fromGit !== null) return fromGit;
|
||||
process.stderr.write(
|
||||
`scan-project: git ls-files unavailable — falling back to recursive walk\n`,
|
||||
);
|
||||
return enumerateViaWalk(projectRoot);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filter accounting
|
||||
//
|
||||
// The project-scanner.md contract requires `filteredByIgnore` to count files
|
||||
// dropped *specifically* by user `.understandignore` patterns (the delta
|
||||
// beyond what the hardcoded defaults would have removed). We accomplish this
|
||||
// by building TWO filters:
|
||||
// - `defaultOnly`: defaults only, no user patterns
|
||||
// - `combined`: defaults + user patterns (createIgnoreFilter)
|
||||
// and counting paths that the combined filter excludes but the defaults-only
|
||||
// filter would have kept.
|
||||
//
|
||||
// Negation (`!pattern`) is correctly handled by the combined filter — a file
|
||||
// re-included via `!` won't be in the combined-excluded set, so it WON'T be
|
||||
// counted in filteredByIgnore (it's "kept", not "additionally filtered").
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Build a defaults-only IgnoreFilter — same patterns as createIgnoreFilter
|
||||
* would apply, minus any user .understandignore content. We synthesize this
|
||||
* via a temp directory with no .understandignore files so the core function
|
||||
* still drives the matcher. (Re-implementing the ignore-package wiring here
|
||||
* would risk subtle behavior drift from core's matcher.)
|
||||
*/
|
||||
function buildDefaultsOnlyFilter() {
|
||||
// Use the createIgnoreFilter with a path that we KNOW has no .understandignore.
|
||||
// `os.tmpdir()`-based fresh dir guarantees no user patterns leak in.
|
||||
// The directory doesn't need to exist on disk because createIgnoreFilter
|
||||
// only checks existsSync() before reading.
|
||||
const fakeProjectRoot = join(
|
||||
require('node:os').tmpdir(),
|
||||
`ua-scan-defaults-${process.pid}-${Date.now()}`,
|
||||
);
|
||||
return createIgnoreFilter(fakeProjectRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether `projectRoot` has any user .understandignore files.
|
||||
* When neither file exists, the combined and defaults-only filters are
|
||||
* identical, so we can skip the dual-filter accounting entirely.
|
||||
*/
|
||||
function hasUserIgnoreFile(projectRoot) {
|
||||
return (
|
||||
existsSync(join(projectRoot, '.understandignore'))
|
||||
|| existsSync(join(projectRoot, '.understand-anything', '.understandignore'))
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Line counting
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Count newline-delimited lines in a file. Returns the number of `\n`
|
||||
* characters; this matches `wc -l` semantics (which counts newlines, not
|
||||
* "lines of content"). Files without a trailing newline therefore report
|
||||
* one fewer than the visible line count — same behavior as wc.
|
||||
*
|
||||
* Per-file failure: emits a Warning: and returns null. Caller decides
|
||||
* whether to drop the file or keep it with sizeLines=0.
|
||||
*/
|
||||
function countLines(absPath, posixPath) {
|
||||
try {
|
||||
const buf = readFileSync(absPath);
|
||||
// Manual newline count beats split('\n').length on large files — no
|
||||
// intermediate array allocation. We count the `\n` byte (0x0a) directly.
|
||||
let count = 0;
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
if (buf[i] === 0x0a) count++;
|
||||
}
|
||||
return count;
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: scan-project: ${posixPath} — line count failed ` +
|
||||
`(${err.message}) — file skipped from output\n`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function main() {
|
||||
const [, , projectRoot, outputPath] = process.argv;
|
||||
if (!projectRoot || !outputPath) {
|
||||
process.stderr.write(
|
||||
'Usage: node scan-project.mjs <projectRoot> <outputPath>\n',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(projectRoot)) {
|
||||
process.stderr.write(
|
||||
`scan-project.mjs failed: projectRoot does not exist: ${projectRoot}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const projectRootStat = statSync(projectRoot);
|
||||
if (!projectRootStat.isDirectory()) {
|
||||
process.stderr.write(
|
||||
`scan-project.mjs failed: projectRoot is not a directory: ${projectRoot}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 1. Enumerate. Either git ls-files or recursive walk.
|
||||
const candidates = enumerateFiles(projectRoot);
|
||||
|
||||
// 2. Filter via createIgnoreFilter (defaults + user .understandignore).
|
||||
// Build a defaults-only filter in parallel to count user-driven drops.
|
||||
const combined = createIgnoreFilter(projectRoot);
|
||||
const userIgnoresPresent = hasUserIgnoreFile(projectRoot);
|
||||
const defaultsOnly = userIgnoresPresent ? buildDefaultsOnlyFilter() : combined;
|
||||
|
||||
let filteredByIgnore = 0;
|
||||
const kept = [];
|
||||
for (const rel of candidates) {
|
||||
const isIgnoredCombined = combined.isIgnored(rel);
|
||||
if (!isIgnoredCombined) {
|
||||
kept.push(rel);
|
||||
continue;
|
||||
}
|
||||
// Dropped by combined filter. If defaults-only would have ALSO dropped
|
||||
// it, this is a baseline default drop — not counted. If defaults-only
|
||||
// would have KEPT it, this drop is attributable to the user's
|
||||
// .understandignore content.
|
||||
if (userIgnoresPresent && !defaultsOnly.isIgnored(rel)) {
|
||||
filteredByIgnore++;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Per-file: language + category + line count.
|
||||
// Drop files that fail line counting (per-file resilience).
|
||||
const fileEntries = [];
|
||||
for (const rel of kept) {
|
||||
const absPath = join(projectRoot, rel);
|
||||
// Stat first — git ls-files could include paths that vanished between
|
||||
// listing and processing; the walker shouldn't but defensive anyway.
|
||||
try {
|
||||
const st = statSync(absPath);
|
||||
if (!st.isFile()) {
|
||||
// Symlinks-to-dir, special files, etc. — skip silently. Not a
|
||||
// warning condition because git wouldn't have tracked it as a file.
|
||||
continue;
|
||||
}
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`Warning: scan-project: ${rel} — stat failed (${err.message}) ` +
|
||||
`— file skipped from output\n`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const sizeLines = countLines(absPath, rel);
|
||||
if (sizeLines === null) {
|
||||
// countLines already emitted the Warning: line.
|
||||
continue;
|
||||
}
|
||||
fileEntries.push({
|
||||
path: rel,
|
||||
language: detectLanguage(rel),
|
||||
sizeLines,
|
||||
fileCategory: detectCategory(rel),
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Determinism: sort by path.localeCompare.
|
||||
fileEntries.sort((a, b) => a.path.localeCompare(b.path));
|
||||
|
||||
// 5. Stats.
|
||||
const byCategory = {};
|
||||
const byLanguage = {};
|
||||
for (const f of fileEntries) {
|
||||
byCategory[f.fileCategory] = (byCategory[f.fileCategory] || 0) + 1;
|
||||
byLanguage[f.language] = (byLanguage[f.language] || 0) + 1;
|
||||
}
|
||||
|
||||
const estimatedComplexity = estimateComplexity(fileEntries.length);
|
||||
|
||||
const output = {
|
||||
scriptCompleted: true,
|
||||
files: fileEntries,
|
||||
totalFiles: fileEntries.length,
|
||||
filteredByIgnore,
|
||||
estimatedComplexity,
|
||||
stats: {
|
||||
filesScanned: fileEntries.length,
|
||||
byCategory,
|
||||
byLanguage,
|
||||
},
|
||||
};
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf-8');
|
||||
|
||||
if (!existsSync(outputPath)) {
|
||||
throw new Error(`output file missing after write: ${outputPath}`);
|
||||
}
|
||||
|
||||
process.stderr.write(
|
||||
`scan-project: filesScanned=${fileEntries.length} ` +
|
||||
`filteredByIgnore=${filteredByIgnore} ` +
|
||||
`complexity=${estimatedComplexity}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run only when executed directly as a CLI; importing the module (e.g. from
|
||||
// tests) must not trigger main().
|
||||
//
|
||||
// Canonicalize both sides through realpathSync. Node ESM resolves
|
||||
// import.meta.url through symlinks but pathToFileURL(process.argv[1]) preserves
|
||||
// them, so a raw equality check silently no-ops when the script is invoked via
|
||||
// a symlinked plugin install path (the default in Claude Code / Copilot CLI
|
||||
// caches). See GitHub issue #162.
|
||||
// ---------------------------------------------------------------------------
|
||||
function isCliEntry() {
|
||||
if (!process.argv[1]) return false;
|
||||
try {
|
||||
const modulePath = realpathSync(fileURLToPath(import.meta.url));
|
||||
const argvPath = realpathSync(process.argv[1]);
|
||||
return modulePath === argvPath;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCliEntry()) {
|
||||
try {
|
||||
await main();
|
||||
} catch (err) {
|
||||
process.stderr.write(`scan-project.mjs failed: ${err.message}\n${err.stack}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Default export of helpers for testability.
|
||||
export default {
|
||||
detectLanguage,
|
||||
detectCategory,
|
||||
estimateComplexity,
|
||||
};
|
||||
Reference in New Issue
Block a user