kirograph 0.28.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/architecture/index.js +4 -1
- package/dist/architecture/index.js.map +2 -2
- package/dist/architecture/manifest/index.js +13 -2
- package/dist/architecture/manifest/index.js.map +2 -2
- package/dist/architecture/manifest/python.js +2 -2
- package/dist/architecture/manifest/python.js.map +2 -2
- package/dist/bin/commands/help.js +3 -1
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/install.js +2 -1
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/memory.js +18 -8
- package/dist/bin/commands/memory.js.map +2 -2
- package/dist/bin/commands/reachability.js +7 -2
- package/dist/bin/commands/reachability.js.map +2 -2
- package/dist/bin/commands/status.js +2 -1
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/uninit.js +2 -1
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vulns.js +26 -6
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/commands/wiki.js +56 -0
- package/dist/bin/commands/wiki.js.map +2 -2
- package/dist/bin/installer/common.js +7 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/detect.js +10 -0
- package/dist/bin/installer/detect.js.map +2 -2
- package/dist/bin/installer/hooks.js +13 -8
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +1 -1
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +1 -1
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +1 -1
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/commandcode.js +84 -0
- package/dist/bin/installer/targets/commandcode.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +1 -1
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +2 -2
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +1 -1
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +1 -1
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/index.js +10 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +1 -1
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +1 -1
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/bin/kirograph.js.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +18 -6
- package/dist/core/pipeline.js.map +2 -2
- package/dist/extraction/extractor.js +88 -26
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +38 -3
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.js.map +2 -2
- package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
- package/dist/frameworks/index.js +68 -7
- package/dist/frameworks/index.js.map +2 -2
- package/dist/jev/client.js +99 -0
- package/dist/jev/client.js.map +7 -0
- package/dist/jev/types.js +17 -0
- package/dist/jev/types.js.map +7 -0
- package/dist/mcp/handlers/memory.js +14 -8
- package/dist/mcp/handlers/memory.js.map +2 -2
- package/dist/mcp/handlers/security.js +5 -3
- package/dist/mcp/handlers/security.js.map +2 -2
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/memory/index.js +13 -1
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/relation-jev.js +59 -0
- package/dist/memory/relation-jev.js.map +7 -0
- package/dist/memory/schema.js +57 -0
- package/dist/memory/schema.js.map +7 -0
- package/dist/security/auth-detection-jev.js +47 -0
- package/dist/security/auth-detection-jev.js.map +7 -0
- package/dist/security/manifest/adapter.js +34 -5
- package/dist/security/manifest/adapter.js.map +3 -3
- package/dist/security/manifest/parser.js +2 -2
- package/dist/security/manifest/parser.js.map +2 -2
- package/dist/security/manifest/plugins/cargo.js +23 -0
- package/dist/security/manifest/plugins/cargo.js.map +2 -2
- package/dist/security/manifest/plugins/composer.js +12 -0
- package/dist/security/manifest/plugins/composer.js.map +2 -2
- package/dist/security/manifest/plugins/gradle.js +12 -0
- package/dist/security/manifest/plugins/gradle.js.map +2 -2
- package/dist/security/manifest/plugins/hex.js +12 -0
- package/dist/security/manifest/plugins/hex.js.map +2 -2
- package/dist/security/manifest/plugins/maven.js +68 -1
- package/dist/security/manifest/plugins/maven.js.map +2 -2
- package/dist/security/manifest/plugins/npm.js +86 -0
- package/dist/security/manifest/plugins/npm.js.map +2 -2
- package/dist/security/manifest/plugins/nuget.js +13 -0
- package/dist/security/manifest/plugins/nuget.js.map +2 -2
- package/dist/security/manifest/plugins/pubspec.js +12 -0
- package/dist/security/manifest/plugins/pubspec.js.map +2 -2
- package/dist/security/manifest/plugins/pyproject.js +12 -0
- package/dist/security/manifest/plugins/pyproject.js.map +2 -2
- package/dist/security/manifest/plugins/rubygems.js +12 -0
- package/dist/security/manifest/plugins/rubygems.js.map +2 -2
- package/dist/security/pipeline.js +5 -2
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +115 -50
- package/dist/security/reachability.js.map +2 -2
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/client.js +45 -1
- package/dist/security/vuln/client.js.map +2 -2
- package/dist/security/vuln/epss-client.js +1 -1
- package/dist/security/vuln/epss-client.js.map +1 -1
- package/dist/security/vuln/osv-adapter.js +144 -32
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/sync/index.js +9 -2
- package/dist/sync/index.js.map +2 -2
- package/dist/vectors/libsql-index.js +287 -0
- package/dist/vectors/libsql-index.js.map +7 -0
- package/dist/vectors/turso-index.js +307 -0
- package/dist/vectors/turso-index.js.map +7 -0
- package/dist/wiki/contradiction-jev.js +49 -0
- package/dist/wiki/contradiction-jev.js.map +7 -0
- package/dist/wiki/index.js +13 -0
- package/dist/wiki/index.js.map +2 -2
- package/dist/wiki/ingest.js +2 -0
- package/dist/wiki/ingest.js.map +2 -2
- package/dist/wiki/links.js +53 -0
- package/dist/wiki/links.js.map +7 -0
- package/dist/wiki/lint.js +3 -6
- package/dist/wiki/lint.js.map +2 -2
- package/dist/wiki/rename.js +103 -0
- package/dist/wiki/rename.js.map +7 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -43,9 +43,9 @@ The result is fewer tool calls, less context used, and faster responses on compl
|
|
|
43
43
|
| <h4>Security</h4> | |
|
|
44
44
|
| 🔒 **Security (KiroGraph-Sec opt-in module)** | Goes beyond "this dependency has a CVE" — uses the call graph to determine if vulnerable code is **actually reachable** from your entry points. Maps your **attack surface** (which HTTP routes reach vulnerable deps). Detects **hardcoded secrets** and shows how many entry points expose them. **SAST-lite** finds SQL injection, path traversal, and dangerous eval in your code. **AST-based SAST (opt-in via `enablePatterns`)** runs 10 bundled structural pattern rules via `@ast-grep/napi` — matches actual code structure, not just symbol names. **Supply chain health** checks OpenSSF Scorecard scores and detects dependency confusion attacks. Covers 14 ecosystems, outputs CycloneDX SBOM/VEX and CI-ready SARIF reports. |
|
|
45
45
|
| <h4>Knowledge & Data</h4> | |
|
|
46
|
-
| 🧠 **Persistent Memory (KiroGraph-Mem opt-in module)** | Cross-session observations — decisions, errors, patterns — auto-linked to code symbols. **Conflict detection**: typed relations between observations (`supersedes`, `conflicts_with`, `compatible`) with agent judgment workflow (`kirograph_mem_compare`, `kirograph_mem_judge`). **Stale review**: schedule observations for re-evaluation (`review_after` + `kirograph_mem_review`). **Passive capture**: extract learnings from structured text. **Prompt saving**: session context reconstruction. **Topic key**: stable semantic addressing. Inspired by [Engram](https://github.com/Gentleman-Programming/engram). |
|
|
46
|
+
| 🧠 **Persistent Memory (KiroGraph-Mem opt-in module)** | Cross-session observations — decisions, errors, patterns — auto-linked to code symbols. **Conflict detection**: typed relations between observations (`supersedes`, `conflicts_with`, `compatible`) with agent judgment workflow (`kirograph_mem_compare`, `kirograph_mem_judge`). **Stale review**: schedule observations for re-evaluation (`review_after` + `kirograph_mem_review`). **Passive capture**: extract learnings from structured text. **Prompt saving**: session context reconstruction. **Topic key**: stable semantic addressing. **Strict writes (opt-in via `memoryStrictWrites`)**: rejects an unknown `kind` or blank tags instead of silently corrupting the store. Inspired by [Engram](https://github.com/Gentleman-Programming/engram) and [IWE](https://github.com/iwe-org/iwe)'s schema-validated agent writes. |
|
|
47
47
|
| 👁️ **Watchmen (KiroGraph-Watchmen opt-in module)** ⚠️ *experimental* | Auto-synthesizes accumulated memory observations into workspace briefs and `inclusion: manual` skill files. Fires via the `watchmenReady` signal in `kirograph_mem_store` when threshold is reached. **Local model** (default, `watchmenSynthesisMode: 'local'`): runs `gemma-4-E4B-it-ONNX` on-device via `@huggingface/transformers` — ~3–4 GB one-time download, ~3–5 GB RAM, 8–15 s on Apple Silicon M1+. No API key, no background daemon, no external calls. **Agent mode** also available for Kiro (`watchmenSynthesisMode: 'agent'`, uses active session). ⚠️ **Experimental:** output quality in local mode depends heavily on the model chosen and your hardware. Smaller models or slower machines may produce incomplete briefs and lower-quality skill files. Use `agent` mode for best results. |
|
|
48
|
-
| 📚 **Wiki (KiroGraph-Wiki opt-in module)** | Karpathy-style LLM wiki — a set of markdown pages that compound knowledge across sessions. Three ops: **ingest** (build structured prompt from source text), **apply-diff** (write LLM-generated WIKI_DIFF to SQLite + disk), **lint** (broken links, orphan pages, contradictions). Context enrichment: `kirograph_context` auto-includes relevant wiki pages above score threshold. Two synthesis modes: `agent` (IDE LLM) or `local` (HuggingFace, same infra as Watchmen). Conflict resolution: deterministic by source date when `wikiAutoResolveConflicts: true`. Inspired by [Karpathy's LLM Wiki pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f). |
|
|
48
|
+
| 📚 **Wiki (KiroGraph-Wiki opt-in module)** | Karpathy-style LLM wiki — a set of markdown pages that compound knowledge across sessions. Three ops: **ingest** (build structured prompt from source text), **apply-diff** (write LLM-generated WIKI_DIFF to SQLite + disk), **lint** (broken links, orphan pages, contradictions). **Link graph** (`kirograph wiki links <slug>`): outgoing/broken/incoming `[[slug]]` backlinks for a page. **Rename** (`kirograph wiki rename <from> <to>`): moves a page's file and rewrites every `[[slug]]` reference to it across the wiki — a CLI-first port of the navigation/refactor features [IWE](https://github.com/iwe-org/iwe) gets from a full LSP. Context enrichment: `kirograph_context` auto-includes relevant wiki pages above score threshold. Two synthesis modes: `agent` (IDE LLM) or `local` (HuggingFace, same infra as Watchmen). Conflict resolution: deterministic by source date when `wikiAutoResolveConflicts: true`. Inspired by [Karpathy's LLM Wiki pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) and [IWE](https://github.com/iwe-org/iwe). |
|
|
49
49
|
| 📖 **Documentation Indexing (KiroGraph-Doc opt-in module)** | Section-level retrieval from Markdown, MDX, RST, AsciiDoc, OpenAPI — 92-97% token savings |
|
|
50
50
|
| 📊 **Data Navigation (KiroGraph-Data opt-in module)** | Query CSV/JSON/Excel/Parquet/**PDF** with filters, aggregations, joins — all server-side in SQLite |
|
|
51
51
|
| <h4>Token Optimization</h4> | |
|
|
@@ -176,6 +176,7 @@ KiroGraph is inspired by [CodeGraph](https://github.com/colbymchenry/codegraph)
|
|
|
176
176
|
- [caveman](https://github.com/JuliusBrussee/caveman) by [Julius Brussee](https://www.linkedin.com/in/julius-brussee/): the caveman mode's agent prose compression concept, multi-level steering injection.
|
|
177
177
|
- [watchmen](https://github.com/firstbatchxyz/watchmen) by [firstbatch](https://github.com/firstbatchxyz): the watchmen module's session-mining concept, workspace brief generation, and `AGENTS.md` mirroring pattern.
|
|
178
178
|
- [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) by [Andrej Karpathy](https://karpathy.ai/): the wiki module's three-op pattern (ingest → apply → lint), WIKI_DIFF block format, two-tool ingest flow, and the principle of knowledge compounding rather than accumulating.
|
|
179
|
+
- [IWE](https://github.com/iwe-org/iwe): a markdown knowledge-graph tool with a full LSP integration and schema-validated agent writes (`expect` guards, per-type document schemas) over MCP. `memoryStrictWrites` (opt-in observation schema validation) and `kirograph wiki links`/`kirograph wiki rename` (link-graph navigation and rename-with-reference-updates, ported to the CLI rather than a full LSP server) both scale down ideas from IWE to fit KiroGraph's existing memory and wiki modules.
|
|
179
180
|
- [jDocMunch-MCP](https://github.com/jgravelle/jdocmunch-mcp) by [J. Gravelle](https://www.linkedin.com/in/j-gravelle-2778223/): the documentation module's section-first retrieval approach, stable section IDs, and byte-offset addressing.
|
|
180
181
|
- [jDataMunch-MCP](https://github.com/jgravelle/jdatamunch-mcp) by [J. Gravelle](https://www.linkedin.com/in/j-gravelle-2778223/): the data module's column profiling, streaming parsers, and server-side aggregation approach.
|
|
181
182
|
- [rtk](https://github.com/rtk-ai/rtk) by [rtk-ai](https://github.com/rtk-ai): the shell compression module's command-family approach and token-optimized output patterns.
|
|
@@ -32,7 +32,10 @@ class ArchitectureAnalyzer {
|
|
|
32
32
|
async analyze(onProgress) {
|
|
33
33
|
const log = (msg) => onProgress?.(msg);
|
|
34
34
|
log("architecture: parsing manifests");
|
|
35
|
-
const manifestPackages = await (0, import_manifest.parseAllManifests)(this.projectRoot
|
|
35
|
+
const manifestPackages = await (0, import_manifest.parseAllManifests)(this.projectRoot, {
|
|
36
|
+
include: this.config.include,
|
|
37
|
+
exclude: this.config.exclude
|
|
38
|
+
});
|
|
36
39
|
const allFiles = this.db.getAllFiles().map((f) => f.path);
|
|
37
40
|
log("architecture: assigning files to packages");
|
|
38
41
|
const filePackageMap = /* @__PURE__ */ new Map();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/architecture/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * ArchitectureAnalyzer\n *\n * Orchestrates the full architecture analysis pipeline:\n * 1. Parse manifests \u2192 packages\n * 2. Directory-based fallback for uncovered file trees\n * 3. Detect layers from file paths\n * 4. Roll up package-to-package dependencies from existing import edges\n * 5. Roll up layer-to-layer dependencies\n * 6. Compute coupling metrics (Ca, Ce, instability)\n *\n * Only runs when enableArchitecture=true. Writes to arch_* tables in kirograph.db.\n */\nimport * as path from 'path';\nimport type { GraphDatabase } from '../db/database';\nimport type { KiroGraphConfig } from '../config';\nimport type {\n ArchPackage, ArchLayer, ArchPackageDep, ArchLayerDep, ArchCoupling, ArchitectureResult,\n} from './types';\nimport { parseAllManifests } from './manifest/index';\nimport { detectAllLayers, buildArchLayers, type FileLayerAssignment } from './layers/index';\n\nexport class ArchitectureAnalyzer {\n constructor(\n private readonly db: GraphDatabase,\n private readonly config: KiroGraphConfig,\n private readonly projectRoot: string,\n ) {}\n\n async analyze(onProgress?: (msg: string) => void): Promise<ArchitectureResult> {\n const log = (msg: string) => onProgress?.(msg);\n\n // \u2500\u2500 1. Parse manifests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: parsing manifests');\n const manifestPackages = await parseAllManifests(this.projectRoot);\n\n // \u2500\u2500 2. Get all indexed files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const allFiles = this.db.getAllFiles().map(f => f.path); // relative paths\n\n // \u2500\u2500 3. Assign files to packages (manifest-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: assigning files to packages');\n const filePackageMap = new Map<string, string>(); // filePath \u2192 packageId\n // Sort packages by path length desc so the most-specific package wins\n const sortedPkgs = [...manifestPackages].sort((a, b) => b.path.length - a.path.length);\n for (const file of allFiles) {\n for (const pkg of sortedPkgs) {\n const pkgPath = pkg.path === '.' ? '' : pkg.path + '/';\n if (pkg.path === '.' || file.startsWith(pkgPath)) {\n filePackageMap.set(file, pkg.id);\n break;\n }\n }\n }\n\n // \u2500\u2500 4. Directory-based fallback / subdivision \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // Three cases:\n // A) No manifests at all \u2192 group by top-level directory.\n // B) Root manifest (path='.') covers everything \u2192 subdivide its files into\n // second-level directory packages so internal structure is visible.\n // C) Manifests exist but don't cover the whole tree \u2192 create directory\n // packages for uncovered subdirectories (threshold: 3 files).\n //\n const SUBDIR_THRESHOLD = 1;\n const directoryPackages: ArchPackage[] = [];\n\n const rootManifestIds = new Set(manifestPackages.filter(p => p.path === '.').map(p => p.id));\n const hasRootManifest = rootManifestIds.size > 0;\n\n if (manifestPackages.length === 0) {\n // Case A: no manifests \u2014 group by top-level directory\n const topDirs = new Set<string>();\n for (const file of allFiles) {\n const parts = file.split('/');\n if (parts.length > 1) topDirs.add(parts[0]);\n }\n for (const dir of topDirs) {\n const pkgId = `pkg:dir:${dir}`;\n directoryPackages.push({ id: pkgId, name: dir, path: dir, source: 'directory', updatedAt: Date.now() });\n for (const file of allFiles) {\n if (file.startsWith(dir + '/')) filePackageMap.set(file, pkgId);\n }\n }\n } else if (hasRootManifest) {\n // Case B: root manifest(s) cover everything \u2014 subdivide into second-level dirs.\n // Collect files owned by a root manifest, then re-assign them to more specific\n // directory packages so internal module boundaries become visible.\n const rootOwnedFiles = allFiles.filter(f => rootManifestIds.has(filePackageMap.get(f) ?? ''));\n const subDirCounts = new Map<string, number>();\n for (const file of rootOwnedFiles) {\n const parts = file.split('/');\n if (parts.length > 2) {\n // Use two-level path (e.g. src/mcp) for granularity inside a src root\n const dir = parts.slice(0, 2).join('/');\n subDirCounts.set(dir, (subDirCounts.get(dir) ?? 0) + 1);\n }\n // Files directly at one level deep (e.g. src/index.ts) stay with the\n // root manifest package \u2014 no single-level dir package is created.\n }\n for (const [dir, count] of subDirCounts) {\n if (count >= SUBDIR_THRESHOLD) {\n const pkgId = `pkg:dir:${dir}`;\n const name = dir.split('/').pop() ?? dir;\n const pkg: ArchPackage = { id: pkgId, name, path: dir, source: 'directory', updatedAt: Date.now() };\n directoryPackages.push(pkg);\n for (const file of rootOwnedFiles) {\n if (file.startsWith(dir + '/') || file === dir) {\n filePackageMap.set(file, pkgId);\n }\n }\n }\n }\n } else {\n // Case C: manifests exist but don't cover root \u2014 create dir packages for uncovered files\n const uncovered = allFiles.filter(f => !filePackageMap.has(f));\n const subDirCounts = new Map<string, number>();\n for (const file of uncovered) {\n const parts = file.split('/');\n if (parts.length > 1) {\n const dir = parts.slice(0, 2).join('/');\n subDirCounts.set(dir, (subDirCounts.get(dir) ?? 0) + 1);\n }\n }\n for (const [dir, count] of subDirCounts) {\n if (count >= SUBDIR_THRESHOLD) {\n const pkgId = `pkg:dir:${dir}`;\n const name = dir.split('/').pop() ?? dir;\n const pkg: ArchPackage = { id: pkgId, name, path: dir, source: 'directory', updatedAt: Date.now() };\n directoryPackages.push(pkg);\n for (const file of uncovered) {\n if (file.startsWith(dir + '/') && !filePackageMap.has(file)) {\n filePackageMap.set(file, pkgId);\n }\n }\n }\n }\n }\n\n const allPackages = [...manifestPackages, ...directoryPackages];\n\n // \u2500\u2500 5. Detect layers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: detecting layers');\n const layerAssignments: FileLayerAssignment[] = [];\n const fileLayerMap = new Map<string, FileLayerAssignment>();\n\n if (this.config.architectureLayers !== undefined || this.config.enableArchitecture) {\n const assignments = await detectAllLayers(allFiles, this.projectRoot, this.config.architectureLayers);\n for (const a of assignments) {\n layerAssignments.push(a);\n fileLayerMap.set(a.filePath, a);\n }\n }\n\n const allLayers = buildArchLayers(layerAssignments, this.config.architectureLayers);\n\n // \u2500\u2500 6. Roll up package dependencies from import edges \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: rolling up package dependencies');\n const importPairs = this.db.getFileImportPairs();\n const pkgDepCounts = new Map<string, Map<string, { count: number; files: Array<{ from: string; to: string }> }>>();\n\n for (const { sourceFile, targetFile } of importPairs) {\n const sourcePkg = filePackageMap.get(sourceFile);\n const targetPkg = filePackageMap.get(targetFile);\n if (!sourcePkg || !targetPkg || sourcePkg === targetPkg) continue;\n\n if (!pkgDepCounts.has(sourcePkg)) pkgDepCounts.set(sourcePkg, new Map());\n const inner = pkgDepCounts.get(sourcePkg)!;\n if (!inner.has(targetPkg)) inner.set(targetPkg, { count: 0, files: [] });\n const entry = inner.get(targetPkg)!;\n entry.count++;\n if (entry.files.length < 5) entry.files.push({ from: sourceFile, to: targetFile });\n }\n\n const packageDeps: ArchPackageDep[] = [];\n for (const [sourcePkg, targets] of pkgDepCounts) {\n for (const [targetPkg, { count, files }] of targets) {\n packageDeps.push({ sourcePkg, targetPkg, depCount: count, files });\n }\n }\n\n // \u2500\u2500 7. Roll up layer dependencies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const layerDepCounts = new Map<string, Map<string, number>>();\n for (const dep of packageDeps) {\n const srcFile = [...filePackageMap.entries()].find(([, pkgId]) => pkgId === dep.sourcePkg)?.[0];\n const tgtFile = [...filePackageMap.entries()].find(([, pkgId]) => pkgId === dep.targetPkg)?.[0];\n const srcLayer = srcFile ? fileLayerMap.get(srcFile)?.layerName : undefined;\n const tgtLayer = tgtFile ? fileLayerMap.get(tgtFile)?.layerName : undefined;\n if (!srcLayer || !tgtLayer || srcLayer === tgtLayer) continue;\n const srcLayerId = `layer:${srcLayer}`;\n const tgtLayerId = `layer:${tgtLayer}`;\n if (!layerDepCounts.has(srcLayerId)) layerDepCounts.set(srcLayerId, new Map());\n const inner = layerDepCounts.get(srcLayerId)!;\n inner.set(tgtLayerId, (inner.get(tgtLayerId) ?? 0) + dep.depCount);\n }\n\n const layerDeps: ArchLayerDep[] = [];\n for (const [srcLayer, targets] of layerDepCounts) {\n for (const [tgtLayer, count] of targets) {\n layerDeps.push({ sourceLayer: srcLayer, targetLayer: tgtLayer, depCount: count });\n }\n }\n\n // \u2500\u2500 8. Compute coupling metrics \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const afferentMap = new Map<string, number>(); // Ca: who depends on me\n const efferentMap = new Map<string, number>(); // Ce: who I depend on\n for (const dep of packageDeps) {\n efferentMap.set(dep.sourcePkg, (efferentMap.get(dep.sourcePkg) ?? 0) + 1);\n afferentMap.set(dep.targetPkg, (afferentMap.get(dep.targetPkg) ?? 0) + 1);\n }\n const coupling: ArchCoupling[] = allPackages.map(pkg => {\n const ca = afferentMap.get(pkg.id) ?? 0;\n const ce = efferentMap.get(pkg.id) ?? 0;\n const instability = ca + ce === 0 ? 0 : ce / (ca + ce);\n return { packageId: pkg.id, afferent: ca, efferent: ce, instability, updatedAt: Date.now() };\n });\n\n // \u2500\u2500 9. Persist to DB \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: persisting to database');\n this.db.clearArchitecture();\n\n for (const pkg of allPackages) this.db.upsertArchPackage(pkg);\n for (const layer of allLayers) this.db.upsertArchLayer(layer);\n\n for (const [filePath, pkgId] of filePackageMap) this.db.upsertArchFilePackage(filePath, pkgId);\n for (const a of layerAssignments) {\n this.db.upsertArchFileLayer(a.filePath, `layer:${a.layerName}`, a.confidence, a.matchedPattern);\n }\n\n for (const dep of packageDeps) {\n this.db.upsertArchPackageDep(dep.sourcePkg, dep.targetPkg, dep.depCount, dep.files);\n }\n for (const dep of layerDeps) {\n this.db.upsertArchLayerDep(dep.sourceLayer, dep.targetLayer, dep.depCount);\n }\n for (const c of coupling) this.db.upsertArchCoupling(c);\n\n // Build return value\n const filePackages: Record<string, string[]> = {};\n for (const [filePath, pkgId] of filePackageMap) {\n if (!filePackages[filePath]) filePackages[filePath] = [];\n filePackages[filePath].push(pkgId);\n }\n\n const fileLayers: Record<string, Array<{ layerId: string; confidence: number; matchedPattern: string }>> = {};\n for (const a of layerAssignments) {\n fileLayers[a.filePath] = [{ layerId: `layer:${a.layerName}`, confidence: a.confidence, matchedPattern: a.matchedPattern }];\n }\n\n return { packages: allPackages, layers: allLayers, packageDeps, layerDeps, coupling, filePackages, fileLayers };\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,sBAAkC;AAClC,oBAA2E;AAEpE,MAAM,qBAAqB;AAAA,EAChC,YACmB,IACA,QACA,aACjB;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,QAAQ,YAAiE;AAC7E,UAAM,MAAM,CAAC,QAAgB,aAAa,GAAG;AAG7C,QAAI,iCAAiC;AACrC,UAAM,mBAAmB,UAAM,mCAAkB,KAAK,
|
|
4
|
+
"sourcesContent": ["/**\n * ArchitectureAnalyzer\n *\n * Orchestrates the full architecture analysis pipeline:\n * 1. Parse manifests \u2192 packages\n * 2. Directory-based fallback for uncovered file trees\n * 3. Detect layers from file paths\n * 4. Roll up package-to-package dependencies from existing import edges\n * 5. Roll up layer-to-layer dependencies\n * 6. Compute coupling metrics (Ca, Ce, instability)\n *\n * Only runs when enableArchitecture=true. Writes to arch_* tables in kirograph.db.\n */\nimport * as path from 'path';\nimport type { GraphDatabase } from '../db/database';\nimport type { KiroGraphConfig } from '../config';\nimport type {\n ArchPackage, ArchLayer, ArchPackageDep, ArchLayerDep, ArchCoupling, ArchitectureResult,\n} from './types';\nimport { parseAllManifests } from './manifest/index';\nimport { detectAllLayers, buildArchLayers, type FileLayerAssignment } from './layers/index';\n\nexport class ArchitectureAnalyzer {\n constructor(\n private readonly db: GraphDatabase,\n private readonly config: KiroGraphConfig,\n private readonly projectRoot: string,\n ) {}\n\n async analyze(onProgress?: (msg: string) => void): Promise<ArchitectureResult> {\n const log = (msg: string) => onProgress?.(msg);\n\n // \u2500\u2500 1. Parse manifests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: parsing manifests');\n const manifestPackages = await parseAllManifests(this.projectRoot, {\n include: this.config.include,\n exclude: this.config.exclude,\n });\n\n // \u2500\u2500 2. Get all indexed files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const allFiles = this.db.getAllFiles().map(f => f.path); // relative paths\n\n // \u2500\u2500 3. Assign files to packages (manifest-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: assigning files to packages');\n const filePackageMap = new Map<string, string>(); // filePath \u2192 packageId\n // Sort packages by path length desc so the most-specific package wins\n const sortedPkgs = [...manifestPackages].sort((a, b) => b.path.length - a.path.length);\n for (const file of allFiles) {\n for (const pkg of sortedPkgs) {\n const pkgPath = pkg.path === '.' ? '' : pkg.path + '/';\n if (pkg.path === '.' || file.startsWith(pkgPath)) {\n filePackageMap.set(file, pkg.id);\n break;\n }\n }\n }\n\n // \u2500\u2500 4. Directory-based fallback / subdivision \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // Three cases:\n // A) No manifests at all \u2192 group by top-level directory.\n // B) Root manifest (path='.') covers everything \u2192 subdivide its files into\n // second-level directory packages so internal structure is visible.\n // C) Manifests exist but don't cover the whole tree \u2192 create directory\n // packages for uncovered subdirectories (threshold: 3 files).\n //\n const SUBDIR_THRESHOLD = 1;\n const directoryPackages: ArchPackage[] = [];\n\n const rootManifestIds = new Set(manifestPackages.filter(p => p.path === '.').map(p => p.id));\n const hasRootManifest = rootManifestIds.size > 0;\n\n if (manifestPackages.length === 0) {\n // Case A: no manifests \u2014 group by top-level directory\n const topDirs = new Set<string>();\n for (const file of allFiles) {\n const parts = file.split('/');\n if (parts.length > 1) topDirs.add(parts[0]);\n }\n for (const dir of topDirs) {\n const pkgId = `pkg:dir:${dir}`;\n directoryPackages.push({ id: pkgId, name: dir, path: dir, source: 'directory', updatedAt: Date.now() });\n for (const file of allFiles) {\n if (file.startsWith(dir + '/')) filePackageMap.set(file, pkgId);\n }\n }\n } else if (hasRootManifest) {\n // Case B: root manifest(s) cover everything \u2014 subdivide into second-level dirs.\n // Collect files owned by a root manifest, then re-assign them to more specific\n // directory packages so internal module boundaries become visible.\n const rootOwnedFiles = allFiles.filter(f => rootManifestIds.has(filePackageMap.get(f) ?? ''));\n const subDirCounts = new Map<string, number>();\n for (const file of rootOwnedFiles) {\n const parts = file.split('/');\n if (parts.length > 2) {\n // Use two-level path (e.g. src/mcp) for granularity inside a src root\n const dir = parts.slice(0, 2).join('/');\n subDirCounts.set(dir, (subDirCounts.get(dir) ?? 0) + 1);\n }\n // Files directly at one level deep (e.g. src/index.ts) stay with the\n // root manifest package \u2014 no single-level dir package is created.\n }\n for (const [dir, count] of subDirCounts) {\n if (count >= SUBDIR_THRESHOLD) {\n const pkgId = `pkg:dir:${dir}`;\n const name = dir.split('/').pop() ?? dir;\n const pkg: ArchPackage = { id: pkgId, name, path: dir, source: 'directory', updatedAt: Date.now() };\n directoryPackages.push(pkg);\n for (const file of rootOwnedFiles) {\n if (file.startsWith(dir + '/') || file === dir) {\n filePackageMap.set(file, pkgId);\n }\n }\n }\n }\n } else {\n // Case C: manifests exist but don't cover root \u2014 create dir packages for uncovered files\n const uncovered = allFiles.filter(f => !filePackageMap.has(f));\n const subDirCounts = new Map<string, number>();\n for (const file of uncovered) {\n const parts = file.split('/');\n if (parts.length > 1) {\n const dir = parts.slice(0, 2).join('/');\n subDirCounts.set(dir, (subDirCounts.get(dir) ?? 0) + 1);\n }\n }\n for (const [dir, count] of subDirCounts) {\n if (count >= SUBDIR_THRESHOLD) {\n const pkgId = `pkg:dir:${dir}`;\n const name = dir.split('/').pop() ?? dir;\n const pkg: ArchPackage = { id: pkgId, name, path: dir, source: 'directory', updatedAt: Date.now() };\n directoryPackages.push(pkg);\n for (const file of uncovered) {\n if (file.startsWith(dir + '/') && !filePackageMap.has(file)) {\n filePackageMap.set(file, pkgId);\n }\n }\n }\n }\n }\n\n const allPackages = [...manifestPackages, ...directoryPackages];\n\n // \u2500\u2500 5. Detect layers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: detecting layers');\n const layerAssignments: FileLayerAssignment[] = [];\n const fileLayerMap = new Map<string, FileLayerAssignment>();\n\n if (this.config.architectureLayers !== undefined || this.config.enableArchitecture) {\n const assignments = await detectAllLayers(allFiles, this.projectRoot, this.config.architectureLayers);\n for (const a of assignments) {\n layerAssignments.push(a);\n fileLayerMap.set(a.filePath, a);\n }\n }\n\n const allLayers = buildArchLayers(layerAssignments, this.config.architectureLayers);\n\n // \u2500\u2500 6. Roll up package dependencies from import edges \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: rolling up package dependencies');\n const importPairs = this.db.getFileImportPairs();\n const pkgDepCounts = new Map<string, Map<string, { count: number; files: Array<{ from: string; to: string }> }>>();\n\n for (const { sourceFile, targetFile } of importPairs) {\n const sourcePkg = filePackageMap.get(sourceFile);\n const targetPkg = filePackageMap.get(targetFile);\n if (!sourcePkg || !targetPkg || sourcePkg === targetPkg) continue;\n\n if (!pkgDepCounts.has(sourcePkg)) pkgDepCounts.set(sourcePkg, new Map());\n const inner = pkgDepCounts.get(sourcePkg)!;\n if (!inner.has(targetPkg)) inner.set(targetPkg, { count: 0, files: [] });\n const entry = inner.get(targetPkg)!;\n entry.count++;\n if (entry.files.length < 5) entry.files.push({ from: sourceFile, to: targetFile });\n }\n\n const packageDeps: ArchPackageDep[] = [];\n for (const [sourcePkg, targets] of pkgDepCounts) {\n for (const [targetPkg, { count, files }] of targets) {\n packageDeps.push({ sourcePkg, targetPkg, depCount: count, files });\n }\n }\n\n // \u2500\u2500 7. Roll up layer dependencies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const layerDepCounts = new Map<string, Map<string, number>>();\n for (const dep of packageDeps) {\n const srcFile = [...filePackageMap.entries()].find(([, pkgId]) => pkgId === dep.sourcePkg)?.[0];\n const tgtFile = [...filePackageMap.entries()].find(([, pkgId]) => pkgId === dep.targetPkg)?.[0];\n const srcLayer = srcFile ? fileLayerMap.get(srcFile)?.layerName : undefined;\n const tgtLayer = tgtFile ? fileLayerMap.get(tgtFile)?.layerName : undefined;\n if (!srcLayer || !tgtLayer || srcLayer === tgtLayer) continue;\n const srcLayerId = `layer:${srcLayer}`;\n const tgtLayerId = `layer:${tgtLayer}`;\n if (!layerDepCounts.has(srcLayerId)) layerDepCounts.set(srcLayerId, new Map());\n const inner = layerDepCounts.get(srcLayerId)!;\n inner.set(tgtLayerId, (inner.get(tgtLayerId) ?? 0) + dep.depCount);\n }\n\n const layerDeps: ArchLayerDep[] = [];\n for (const [srcLayer, targets] of layerDepCounts) {\n for (const [tgtLayer, count] of targets) {\n layerDeps.push({ sourceLayer: srcLayer, targetLayer: tgtLayer, depCount: count });\n }\n }\n\n // \u2500\u2500 8. Compute coupling metrics \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const afferentMap = new Map<string, number>(); // Ca: who depends on me\n const efferentMap = new Map<string, number>(); // Ce: who I depend on\n for (const dep of packageDeps) {\n efferentMap.set(dep.sourcePkg, (efferentMap.get(dep.sourcePkg) ?? 0) + 1);\n afferentMap.set(dep.targetPkg, (afferentMap.get(dep.targetPkg) ?? 0) + 1);\n }\n const coupling: ArchCoupling[] = allPackages.map(pkg => {\n const ca = afferentMap.get(pkg.id) ?? 0;\n const ce = efferentMap.get(pkg.id) ?? 0;\n const instability = ca + ce === 0 ? 0 : ce / (ca + ce);\n return { packageId: pkg.id, afferent: ca, efferent: ce, instability, updatedAt: Date.now() };\n });\n\n // \u2500\u2500 9. Persist to DB \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n log('architecture: persisting to database');\n this.db.clearArchitecture();\n\n for (const pkg of allPackages) this.db.upsertArchPackage(pkg);\n for (const layer of allLayers) this.db.upsertArchLayer(layer);\n\n for (const [filePath, pkgId] of filePackageMap) this.db.upsertArchFilePackage(filePath, pkgId);\n for (const a of layerAssignments) {\n this.db.upsertArchFileLayer(a.filePath, `layer:${a.layerName}`, a.confidence, a.matchedPattern);\n }\n\n for (const dep of packageDeps) {\n this.db.upsertArchPackageDep(dep.sourcePkg, dep.targetPkg, dep.depCount, dep.files);\n }\n for (const dep of layerDeps) {\n this.db.upsertArchLayerDep(dep.sourceLayer, dep.targetLayer, dep.depCount);\n }\n for (const c of coupling) this.db.upsertArchCoupling(c);\n\n // Build return value\n const filePackages: Record<string, string[]> = {};\n for (const [filePath, pkgId] of filePackageMap) {\n if (!filePackages[filePath]) filePackages[filePath] = [];\n filePackages[filePath].push(pkgId);\n }\n\n const fileLayers: Record<string, Array<{ layerId: string; confidence: number; matchedPattern: string }>> = {};\n for (const a of layerAssignments) {\n fileLayers[a.filePath] = [{ layerId: `layer:${a.layerName}`, confidence: a.confidence, matchedPattern: a.matchedPattern }];\n }\n\n return { packages: allPackages, layers: allLayers, packageDeps, layerDeps, coupling, filePackages, fileLayers };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -91,8 +91,19 @@ function registerManifestParser(parser) {
|
|
|
91
91
|
if (idx >= 0) MANIFEST_PARSERS[idx] = parser;
|
|
92
92
|
else MANIFEST_PARSERS.push(parser);
|
|
93
93
|
}
|
|
94
|
-
async function parseAllManifests(projectRoot) {
|
|
95
|
-
|
|
94
|
+
async function parseAllManifests(projectRoot, scanOptions) {
|
|
95
|
+
let manifestPaths = _findManifests(projectRoot);
|
|
96
|
+
if (scanOptions?.include && scanOptions.include.length > 0) {
|
|
97
|
+
const picomatch = require("picomatch");
|
|
98
|
+
const includeMatchers = scanOptions.include.map((p) => picomatch(p));
|
|
99
|
+
const excludeMatchers = (scanOptions.exclude ?? []).map((p) => picomatch(p));
|
|
100
|
+
manifestPaths = manifestPaths.filter((absPath) => {
|
|
101
|
+
const rel = path.relative(projectRoot, absPath).replace(/\\/g, "/");
|
|
102
|
+
const included = includeMatchers.some((m) => m(rel));
|
|
103
|
+
if (!included) return false;
|
|
104
|
+
return !excludeMatchers.some((m) => m(rel));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
96
107
|
const all = [];
|
|
97
108
|
const seenIds = /* @__PURE__ */ new Set();
|
|
98
109
|
for (const manifestPath of manifestPaths) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/architecture/manifest/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Manifest Parser Registry\n *\n * Discovers and dispatches manifest parsers. Each parser handles one or more\n * manifest file types. The registry walks the project looking for manifest files\n * and assigns them to the correct parser.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ManifestParser, ArchPackage } from '../types';\nimport { npmParser } from './npm';\nimport { goParser } from './go';\nimport { cargoParser } from './cargo';\nimport { pythonParser } from './python';\nimport { mavenParser } from './maven';\nimport { gradleParser } from './gradle';\nimport { csprojParser } from './csproj';\nimport { sbtParser } from './scala';\nimport { ocamlParser } from './ocaml';\nimport { elmParser } from './elm';\n\n// All registered manifest parsers, in priority order\nconst MANIFEST_PARSERS: ManifestParser[] = [\n npmParser,\n goParser,\n cargoParser,\n pythonParser,\n mavenParser,\n gradleParser,\n csprojParser,\n sbtParser,\n ocamlParser,\n elmParser,\n];\n\n// All manifest filenames we care about (for fast lookup during directory walk)\nconst MANIFEST_FILENAMES = new Set(\n MANIFEST_PARSERS.flatMap(p => p.manifestFiles)\n);\n\n// Directories to never descend into when scanning for manifests\nconst SKIP_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', 'target', '.gradle', '__pycache__',\n '.kirograph', 'vendor', '.cache', 'coverage', '.nyc_output', '_build', '_opam',\n 'elm-stuff', 'zig-cache', 'zig-out',\n]);\n\nexport function getAllManifestParsers(): ManifestParser[] {\n return MANIFEST_PARSERS;\n}\n\nexport function getManifestParser(name: string): ManifestParser | undefined {\n return MANIFEST_PARSERS.find(p => p.name === name);\n}\n\nexport function registerManifestParser(parser: ManifestParser): void {\n const idx = MANIFEST_PARSERS.findIndex(p => p.name === parser.name);\n if (idx >= 0) MANIFEST_PARSERS[idx] = parser;\n else MANIFEST_PARSERS.push(parser);\n}\n\n/**\n * Walk projectRoot looking for manifest files, parse each, deduplicate, and\n * return all detected packages. Packages from manifests take priority; the\n * deduplication ensures that a workspace root pom.xml and a sub-module\n * pom.xml don't produce overlapping directory mappings.\n */\nexport async function parseAllManifests(projectRoot: string): Promise<ArchPackage[]> {\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,SAAoB;AACpB,WAAsB;AAEtB,iBAA0B;AAC1B,gBAAyB;AACzB,mBAA4B;AAC5B,oBAA6B;AAC7B,mBAA4B;AAC5B,oBAA6B;AAC7B,oBAA6B;AAC7B,mBAA0B;AAC1B,mBAA4B;AAC5B,iBAA0B;AAG1B,MAAM,mBAAqC;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,MAAM,qBAAqB,IAAI;AAAA,EAC7B,iBAAiB,QAAQ,OAAK,EAAE,aAAa;AAC/C;AAGA,MAAM,YAAY,oBAAI,IAAI;AAAA,EACxB;AAAA,EAAgB;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAU;AAAA,EAAW;AAAA,EAC9D;AAAA,EAAc;AAAA,EAAU;AAAA,EAAU;AAAA,EAAY;AAAA,EAAe;AAAA,EAAU;AAAA,EACvE;AAAA,EAAa;AAAA,EAAa;AAC5B,CAAC;AAEM,SAAS,wBAA0C;AACxD,SAAO;AACT;AAEO,SAAS,kBAAkB,MAA0C;AAC1E,SAAO,iBAAiB,KAAK,OAAK,EAAE,SAAS,IAAI;AACnD;AAEO,SAAS,uBAAuB,QAA8B;AACnE,QAAM,MAAM,iBAAiB,UAAU,OAAK,EAAE,SAAS,OAAO,IAAI;AAClE,MAAI,OAAO,EAAG,kBAAiB,GAAG,IAAI;AAAA,MACjC,kBAAiB,KAAK,MAAM;AACnC;
|
|
4
|
+
"sourcesContent": ["/**\n * Manifest Parser Registry\n *\n * Discovers and dispatches manifest parsers. Each parser handles one or more\n * manifest file types. The registry walks the project looking for manifest files\n * and assigns them to the correct parser.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ManifestParser, ArchPackage } from '../types';\nimport { npmParser } from './npm';\nimport { goParser } from './go';\nimport { cargoParser } from './cargo';\nimport { pythonParser } from './python';\nimport { mavenParser } from './maven';\nimport { gradleParser } from './gradle';\nimport { csprojParser } from './csproj';\nimport { sbtParser } from './scala';\nimport { ocamlParser } from './ocaml';\nimport { elmParser } from './elm';\n\n// All registered manifest parsers, in priority order\nconst MANIFEST_PARSERS: ManifestParser[] = [\n npmParser,\n goParser,\n cargoParser,\n pythonParser,\n mavenParser,\n gradleParser,\n csprojParser,\n sbtParser,\n ocamlParser,\n elmParser,\n];\n\n// All manifest filenames we care about (for fast lookup during directory walk)\nconst MANIFEST_FILENAMES = new Set(\n MANIFEST_PARSERS.flatMap(p => p.manifestFiles)\n);\n\n// Directories to never descend into when scanning for manifests\nconst SKIP_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', 'target', '.gradle', '__pycache__',\n '.kirograph', 'vendor', '.cache', 'coverage', '.nyc_output', '_build', '_opam',\n 'elm-stuff', 'zig-cache', 'zig-out',\n]);\n\nexport function getAllManifestParsers(): ManifestParser[] {\n return MANIFEST_PARSERS;\n}\n\nexport function getManifestParser(name: string): ManifestParser | undefined {\n return MANIFEST_PARSERS.find(p => p.name === name);\n}\n\nexport function registerManifestParser(parser: ManifestParser): void {\n const idx = MANIFEST_PARSERS.findIndex(p => p.name === parser.name);\n if (idx >= 0) MANIFEST_PARSERS[idx] = parser;\n else MANIFEST_PARSERS.push(parser);\n}\n\nexport interface ManifestScanOptions {\n include?: string[];\n exclude?: string[];\n}\n\n/**\n * Walk projectRoot looking for manifest files, parse each, deduplicate, and\n * return all detected packages. Packages from manifests take priority; the\n * deduplication ensures that a workspace root pom.xml and a sub-module\n * pom.xml don't produce overlapping directory mappings.\n *\n * When scanOptions.include is provided, only manifests within included paths\n * are processed (aligning with the code graph scope).\n */\nexport async function parseAllManifests(projectRoot: string, scanOptions?: ManifestScanOptions): Promise<ArchPackage[]> {\n let manifestPaths = _findManifests(projectRoot);\n\n // Filter by include/exclude if provided\n if (scanOptions?.include && scanOptions.include.length > 0) {\n const picomatch = require('picomatch');\n const includeMatchers = scanOptions.include.map((p: string) => picomatch(p));\n const excludeMatchers = (scanOptions.exclude ?? []).map((p: string) => picomatch(p));\n manifestPaths = manifestPaths.filter(absPath => {\n const rel = path.relative(projectRoot, absPath).replace(/\\\\/g, '/');\n const included = includeMatchers.some((m: (s: string) => boolean) => m(rel));\n if (!included) return false;\n return !excludeMatchers.some((m: (s: string) => boolean) => m(rel));\n });\n }\n\n const all: ArchPackage[] = [];\n const seenIds = new Set<string>();\n\n for (const manifestPath of manifestPaths) {\n const parser = MANIFEST_PARSERS.find(p => p.canParse(manifestPath));\n if (!parser) continue;\n try {\n const pkgs = await parser.parse(manifestPath, projectRoot);\n for (const pkg of pkgs) {\n if (!seenIds.has(pkg.id)) {\n seenIds.add(pkg.id);\n all.push(pkg);\n }\n }\n } catch {\n // Ignore parse errors for individual manifests\n }\n }\n\n return all;\n}\n\nfunction _findManifests(dir: string, results: string[] = []): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name)) {\n _findManifests(path.join(dir, entry.name), results);\n }\n } else if (entry.isFile()) {\n // Check exact filename or extension match\n if (MANIFEST_FILENAMES.has(entry.name) || entry.name.endsWith('.csproj')) {\n results.push(path.join(dir, entry.name));\n }\n }\n }\n\n return results;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -35,11 +35,11 @@ var fs = __toESM(require("fs"));
|
|
|
35
35
|
var path = __toESM(require("path"));
|
|
36
36
|
const pythonParser = {
|
|
37
37
|
name: "python",
|
|
38
|
-
manifestFiles: ["pyproject.toml", "setup.py", "setup.cfg"],
|
|
38
|
+
manifestFiles: ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt"],
|
|
39
39
|
language: "python",
|
|
40
40
|
canParse(manifestPath) {
|
|
41
41
|
const base = path.basename(manifestPath);
|
|
42
|
-
return base === "pyproject.toml" || base === "setup.py" || base === "setup.cfg";
|
|
42
|
+
return base === "pyproject.toml" || base === "setup.py" || base === "setup.cfg" || base === "requirements.txt";
|
|
43
43
|
},
|
|
44
44
|
async parse(manifestPath, projectRoot) {
|
|
45
45
|
let content;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/architecture/manifest/python.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Python manifest parser.\n * Handles pyproject.toml, setup.py, setup.cfg.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ManifestParser, ArchPackage } from '../types';\n\nexport const pythonParser: ManifestParser = {\n name: 'python',\n manifestFiles: ['pyproject.toml', 'setup.py', 'setup.cfg'],\n language: 'python',\n\n canParse(manifestPath: string): boolean {\n const base = path.basename(manifestPath);\n return base === 'pyproject.toml' || base === 'setup.py' || base === 'setup.cfg';\n },\n\n async parse(manifestPath: string, projectRoot: string): Promise<ArchPackage[]> {\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch {\n return [];\n }\n\n const base = path.basename(manifestPath);\n let name: string | undefined;\n let version: string | undefined;\n const externalDeps: string[] = [];\n\n if (base === 'pyproject.toml') {\n name = content.match(/^\\[(?:tool\\.poetry|project)\\][^[]*name\\s*=\\s*\"([^\"]+)\"/ms)?.[1];\n version = content.match(/^\\[(?:tool\\.poetry|project)\\][^[]*version\\s*=\\s*\"([^\"]+)\"/ms)?.[1];\n const depsMatch = content.match(/^\\[(?:tool\\.poetry\\.)?dependencies\\]([\\s\\S]*?)(?=^\\[|\\Z)/m);\n if (depsMatch) {\n for (const line of depsMatch[1].split('\\n')) {\n const m = line.trim().match(/^([a-zA-Z0-9_.-]+)\\s*[=<>!]/);\n if (m && m[1] !== 'python') externalDeps.push(m[1]);\n }\n }\n } else if (base === 'setup.py') {\n name = content.match(/name\\s*=\\s*['\"]([^'\"]+)['\"]/)?.[1];\n version = content.match(/version\\s*=\\s*['\"]([^'\"]+)['\"]/)?.[1];\n const installRequires = content.match(/install_requires\\s*=\\s*\\[([^\\]]+)\\]/s);\n if (installRequires) {\n for (const dep of installRequires[1].matchAll(/['\"]([a-zA-Z0-9_.-]+)/g)) {\n externalDeps.push(dep[1]);\n }\n }\n } else if (base === 'setup.cfg') {\n name = content.match(/^name\\s*=\\s*(.+)/m)?.[1]?.trim();\n version = content.match(/^version\\s*=\\s*(.+)/m)?.[1]?.trim();\n const depsSection = content.match(/^install_requires\\s*=([\\s\\S]*?)(?=^\\[|\\Z)/m);\n if (depsSection) {\n for (const line of depsSection[1].split('\\n')) {\n const m = line.trim().match(/^([a-zA-Z0-9_.-]+)/);\n if (m) externalDeps.push(m[1]);\n }\n }\n }\n\n const relDir = path.relative(projectRoot, path.dirname(manifestPath)).replace(/\\\\/g, '/') || '.';\n const relManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n const pkgName = name ?? path.basename(path.dirname(manifestPath));\n\n return [{\n id: `pkg:python:${relDir}`,\n name: pkgName,\n path: relDir,\n source: 'manifest',\n language: 'python',\n manifestPath: relManifest,\n version,\n externalDeps,\n updatedAt: Date.now(),\n }];\n },\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["/**\n * Python manifest parser.\n * Handles pyproject.toml, setup.py, setup.cfg, requirements.txt.\n *\n * requirements.txt has no name/version/externalDeps of its own for the\n * architecture (import-graph) side \u2014 it's listed here purely so the security\n * module's pip plugin (registered under the same 'python' ecosystem key)\n * gets it through manifest discovery at all; see security/manifest/adapter.ts.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { ManifestParser, ArchPackage } from '../types';\n\nexport const pythonParser: ManifestParser = {\n name: 'python',\n manifestFiles: ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt'],\n language: 'python',\n\n canParse(manifestPath: string): boolean {\n const base = path.basename(manifestPath);\n return base === 'pyproject.toml' || base === 'setup.py' || base === 'setup.cfg' || base === 'requirements.txt';\n },\n\n async parse(manifestPath: string, projectRoot: string): Promise<ArchPackage[]> {\n let content: string;\n try {\n content = fs.readFileSync(manifestPath, 'utf8');\n } catch {\n return [];\n }\n\n const base = path.basename(manifestPath);\n let name: string | undefined;\n let version: string | undefined;\n const externalDeps: string[] = [];\n\n if (base === 'pyproject.toml') {\n name = content.match(/^\\[(?:tool\\.poetry|project)\\][^[]*name\\s*=\\s*\"([^\"]+)\"/ms)?.[1];\n version = content.match(/^\\[(?:tool\\.poetry|project)\\][^[]*version\\s*=\\s*\"([^\"]+)\"/ms)?.[1];\n const depsMatch = content.match(/^\\[(?:tool\\.poetry\\.)?dependencies\\]([\\s\\S]*?)(?=^\\[|\\Z)/m);\n if (depsMatch) {\n for (const line of depsMatch[1].split('\\n')) {\n const m = line.trim().match(/^([a-zA-Z0-9_.-]+)\\s*[=<>!]/);\n if (m && m[1] !== 'python') externalDeps.push(m[1]);\n }\n }\n } else if (base === 'setup.py') {\n name = content.match(/name\\s*=\\s*['\"]([^'\"]+)['\"]/)?.[1];\n version = content.match(/version\\s*=\\s*['\"]([^'\"]+)['\"]/)?.[1];\n const installRequires = content.match(/install_requires\\s*=\\s*\\[([^\\]]+)\\]/s);\n if (installRequires) {\n for (const dep of installRequires[1].matchAll(/['\"]([a-zA-Z0-9_.-]+)/g)) {\n externalDeps.push(dep[1]);\n }\n }\n } else if (base === 'setup.cfg') {\n name = content.match(/^name\\s*=\\s*(.+)/m)?.[1]?.trim();\n version = content.match(/^version\\s*=\\s*(.+)/m)?.[1]?.trim();\n const depsSection = content.match(/^install_requires\\s*=([\\s\\S]*?)(?=^\\[|\\Z)/m);\n if (depsSection) {\n for (const line of depsSection[1].split('\\n')) {\n const m = line.trim().match(/^([a-zA-Z0-9_.-]+)/);\n if (m) externalDeps.push(m[1]);\n }\n }\n }\n\n const relDir = path.relative(projectRoot, path.dirname(manifestPath)).replace(/\\\\/g, '/') || '.';\n const relManifest = path.relative(projectRoot, manifestPath).replace(/\\\\/g, '/');\n const pkgName = name ?? path.basename(path.dirname(manifestPath));\n\n return [{\n id: `pkg:python:${relDir}`,\n name: pkgName,\n path: relDir,\n source: 'manifest',\n language: 'python',\n manifestPath: relManifest,\n version,\n externalDeps,\n updatedAt: Date.now(),\n }];\n },\n};\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,SAAoB;AACpB,WAAsB;AAGf,MAAM,eAA+B;AAAA,EAC1C,MAAM;AAAA,EACN,eAAe,CAAC,kBAAkB,YAAY,aAAa,kBAAkB;AAAA,EAC7E,UAAU;AAAA,EAEV,SAAS,cAA+B;AACtC,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,WAAO,SAAS,oBAAoB,SAAS,cAAc,SAAS,eAAe,SAAS;AAAA,EAC9F;AAAA,EAEA,MAAM,MAAM,cAAsB,aAA6C;AAC7E,QAAI;AACJ,QAAI;AACF,gBAAU,GAAG,aAAa,cAAc,MAAM;AAAA,IAChD,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,QAAI;AACJ,QAAI;AACJ,UAAM,eAAyB,CAAC;AAEhC,QAAI,SAAS,kBAAkB;AAC7B,aAAO,QAAQ,MAAM,0DAA0D,IAAI,CAAC;AACpF,gBAAU,QAAQ,MAAM,6DAA6D,IAAI,CAAC;AAC1F,YAAM,YAAY,QAAQ,MAAM,2DAA2D;AAC3F,UAAI,WAAW;AACb,mBAAW,QAAQ,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG;AAC3C,gBAAM,IAAI,KAAK,KAAK,EAAE,MAAM,6BAA6B;AACzD,cAAI,KAAK,EAAE,CAAC,MAAM,SAAU,cAAa,KAAK,EAAE,CAAC,CAAC;AAAA,QACpD;AAAA,MACF;AAAA,IACF,WAAW,SAAS,YAAY;AAC9B,aAAO,QAAQ,MAAM,6BAA6B,IAAI,CAAC;AACvD,gBAAU,QAAQ,MAAM,gCAAgC,IAAI,CAAC;AAC7D,YAAM,kBAAkB,QAAQ,MAAM,sCAAsC;AAC5E,UAAI,iBAAiB;AACnB,mBAAW,OAAO,gBAAgB,CAAC,EAAE,SAAS,wBAAwB,GAAG;AACvE,uBAAa,KAAK,IAAI,CAAC,CAAC;AAAA,QAC1B;AAAA,MACF;AAAA,IACF,WAAW,SAAS,aAAa;AAC/B,aAAO,QAAQ,MAAM,mBAAmB,IAAI,CAAC,GAAG,KAAK;AACrD,gBAAU,QAAQ,MAAM,sBAAsB,IAAI,CAAC,GAAG,KAAK;AAC3D,YAAM,cAAc,QAAQ,MAAM,4CAA4C;AAC9E,UAAI,aAAa;AACf,mBAAW,QAAQ,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG;AAC7C,gBAAM,IAAI,KAAK,KAAK,EAAE,MAAM,oBAAoB;AAChD,cAAI,EAAG,cAAa,KAAK,EAAE,CAAC,CAAC;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,SAAS,aAAa,KAAK,QAAQ,YAAY,CAAC,EAAE,QAAQ,OAAO,GAAG,KAAK;AAC7F,UAAM,cAAc,KAAK,SAAS,aAAa,YAAY,EAAE,QAAQ,OAAO,GAAG;AAC/E,UAAM,UAAU,QAAQ,KAAK,SAAS,KAAK,QAAQ,YAAY,CAAC;AAEhE,WAAO,CAAC;AAAA,MACN,IAAI,cAAc,MAAM;AAAA,MACxB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -286,6 +286,8 @@ const GROUPS = [
|
|
|
286
286
|
{ name: "wiki page", args: "<slug>", desc: "Print the full content of a wiki page" },
|
|
287
287
|
{ name: "wiki list", desc: "List all wiki pages" },
|
|
288
288
|
{ name: "wiki lint", desc: "Health check the wiki for broken links, orphans, contradictions" },
|
|
289
|
+
{ name: "wiki links", args: "<slug>", desc: "Show outgoing, broken, and incoming [[slug]] links for a page" },
|
|
290
|
+
{ name: "wiki rename", args: "<from> <to>", desc: "Rename a page slug, moving its file and updating every [[slug]] reference to it" },
|
|
289
291
|
{ name: "wiki reindex", desc: "Rebuild SQLite index from .kirograph/wiki/*.md files" },
|
|
290
292
|
{ name: "wiki status", desc: "Wiki subsystem stats: page count, source count, oldest/newest page" },
|
|
291
293
|
{ name: "wiki synthesize", desc: "Run local-model wiki synthesis over the pending source queue" },
|
|
@@ -343,7 +345,7 @@ const GROUPS = [
|
|
|
343
345
|
"--add <cveId> Manually register a CVE (requires --package and --version)"
|
|
344
346
|
]
|
|
345
347
|
},
|
|
346
|
-
{ name: "reachability", args: "<target>", desc: "Check reachability for a CVE or dependency: verdict, call paths, impact" },
|
|
348
|
+
{ name: "reachability", args: "<target>", desc: "Check reachability for a CVE or dependency: verdict, call paths, impact", opts: ["--explain Plain-English reason for the verdict"] },
|
|
347
349
|
{
|
|
348
350
|
name: "vuln suppress",
|
|
349
351
|
args: "<cveId>",
|