Skip to content
This repository was archived by the owner on May 22, 2026. It is now read-only.

release: v0.3.0 - #4

Merged
rangoel-nu merged 75 commits into
mainfrom
develop
Mar 7, 2026
Merged

release: v0.3.0#4
rangoel-nu merged 75 commits into
mainfrom
develop

Conversation

@rangoel-nu

Copy link
Copy Markdown
Contributor

Merge develop into main for the v0.3.0 release.

See PR #3 for full change details.

rangoel-nu and others added 30 commits February 25, 2026 20:10
- Remove StateGraph instantiation → AGENT mapping in langgraph adapter;
  builder variables are not agents, only add_node() calls produce agents
- Remove agent_generic and tool_generic regex adapters that caused
  false-positive nodes named "generic"
- Route SQL/Python schema detections to dc_metadata instead of emitting
  separate DATASTORE nodes; merge PII/PHI classification onto existing
  DATASTORE nodes via _enrich_datastores()
- Add typed data_classification, classified_tables, classified_fields
  fields to NodeMetadata and ScanSummary; update serializer and schema
- Fix duplicate classification data in extras by excluding these keys
  from the bulk metadata → extras copy
- Add LlamaIndex from_tools/from_defaults detection for ReActAgent,
  FunctionTool, and QueryEngineTool class-method builders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- config.py: auto-detect Azure AI Foundry from ANTHROPIC_FOUNDRY_RESOURCE
  env var; default model becomes anthropic/claude-haiku-4-5 with the
  correct /anthropic base URL; add llm_api_base field readable from
  AISBOM_LLM_API_BASE env var
- llm_client.py: add api_base parameter; pass it through to litellm;
  suppress litellm startup noise (vertex credential probes) via
  litellm.suppress_debug_info and logger level overrides
- extractor.py: pass config.llm_api_base to LLMClient constructor
- cli.py: add --llm-api-base flag to both scan subcommands

Also update .env to use anthropic/claude-haiku-4-5 instead of
vertex_ai/gemini-2.5-flash (which requires Google Cloud OAuth2 creds,
not just an API key, causing all LLM calls to fail silently).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x AI API key, per-node evidence

Issue 3 — serializer.py: add exclude_none=True to model_dump_json() so null
NodeMetadata fields are omitted from JSON output (reduces node size from 15+
null fields to just {"extras": {}}).

Issue 1 — llm_clients_ts/prompt_ts: remove synthetic FRAMEWORK nodes emitted
at line 0 with no real source location. Also fix PromptTSAdapter.can_handle()
to delegate to the base class instead of always returning True, preventing
prompt detection from running on every JS/TS file regardless of imports.

Issue 4 — config.py/llm_client.py: add google_api_key (GEMINI_API_KEY /
GOOGLE_CLOUD_API_KEY) and vertex_location to ExtractionConfig. When model is
vertex_ai/* and google_api_key is set, LLMClient bypasses litellm and calls
aiplatform.googleapis.com directly with ?key= query param — matching the
NuGuard-app reference implementation.

Issue 2 — models.py: move evidence list from AiBomDocument to Node.evidence,
eliminating the fragile adapter-name string matching in _build_evidence_map().
Delete _build_evidence_map(); replace with {n.id: n.evidence for n in
doc.nodes}. Bump schema_version 1.0.0 → 1.1.0 and regenerate aibom.schema.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous commit changed PromptTSAdapter.can_handle() to delegate to
the base class, which gates execution on _PROMPT_PACKAGES imports. This
caused prompt nodes to disappear from repos that use prompts without
LangChain/Vercel AI SDK/LlamaIndex (e.g. direct Gemini/OpenAI calls).

The original return True was intentional — prompts appear in any file
and _detect filters false positives. Only the _fw_node() emission needed
to be removed, not the can_handle() override.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
langgraph.py: replace hardcoded display_name=f"prompt_{line}" with a new
_prompt_display_name() helper that derives the name from context (variable
name / class name) or content patterns ("you are" → "System Prompt",
"answer the question" → "RAG Prompt", etc.).

prompts.py: fix _prompt_name() to split camelCase context names before
lowercasing so systemInstruction → "System Instruction" instead of
"Systeminstruction".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n file scan

Adds skip rules to _iter_files():
- .claude/ directory (tool config)
- .github/** except .github/workflows/** (issue templates, PR templates, etc.)
- CLAUDE.md and AGENTS.md at any path (AI tooling instruction files)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x of another

Add two post-accumulation dedup passes in _iter_files pipeline:

_dedup_by_location(): drops accumulators that share (component_type, file,
line) with a higher-priority entry, merging their evidence into the winner.

_dedup_by_name_prefix(): drops accumulators whose display name is a strict
prefix of another same-type entry sharing at least one source file. Handles
the common case where a regex adapter extracts a truncated model name (e.g.
gemini-2.0) while an AST adapter extracts the full string (gemini-2.0-flash)
from an adjacent line of the same call. The shorter entry is absorbed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename velo.sh → xelo.sh and update references throughout
- pyproject.toml: rename CLI entry point velo → xelo, update comment
- README.md, Dockerfile, devcontainer: update branding references
- .gitignore: add output/ directory
- release.yml: update workflow
- TS adapters (bedrock_agents, datastores, google_adk, langgraph, openai_agents):
  various fixes and improvements carried over from prior work
- models_kb.py, merger.py, cdx_tools.py: related cleanup
- tests: update conftest, cyclonedx, merger tests; fix healthcare fixture
  generator field vela → xelo; update patient_portal SQL schema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…drails

- ast_parser: handle Agent[T](...) subscript syntax in _get_call_name() so typed
  generic agents like Agent[AirlineAgentContext](...) are correctly detected
- ast_parser: handle @decorator(kwargs) call-style decorators in visit_FunctionDef
  so @function_tool(name_override="foo") is captured alongside bare @function_tool
- ast_parser: track Runner.run() first-arg vars inside @input_guardrail functions;
  store in ParseResult.guardrail_agent_vars for adapter use
- ast_parser: unwrap await expressions (await call(...)) in visit_Expr and
  visit_Assign so async calls are visited for guardrail tracking
- openai_agents adapter: use guardrail_agent_vars to classify Agent instances as
  GUARDRAIL instead of AGENT when invoked inside @input_guardrail functions
- openai_agents adapter: check name_override kwarg first when extracting tool name
  from @function_tool(name_override="...") decorators
- types: add GUARDRAIL to ComponentType enum
- schemas: regenerate aibom.schema.json with GUARDRAIL component type

Result: openai-cs-agents-demo scan now detects 5 AGENT + 2 GUARDRAIL + 6 TOOL
nodes (was 2 AGENT + 1 TOOL before these fixes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation exception

ast_parser:
- Add ast.JoinedStr (f-string) support in _extract_value() — static text parts
  are joined with {…} placeholders for dynamic expressions, giving adapters a
  meaningful string to work with for instruction detection

openai_agents adapter:
- When instructions= is a function reference ($func_name), look up string
  literals whose context matches that function name in parse_result.string_literals
  and use the longest one as the instruction text
- Use "{agent_name} Instructions" as PROMPT display name instead of "instructions_N"
- Raise PROMPT confidence from 0.85 to 0.92 to match agent confidence level

verification:
- Fix exception handling in verify_uncertain_nodes: on API/network failure, skip
  verification entirely (node keeps original confidence) rather than marking it as
  verified=False which incorrectly drops the node from the final output

Result: openai-cs-agents-demo scan now detects 7 PROMPT nodes covering all agents
(was 0 surviving after LLM verification due to Anthropic API key exception)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
content_preview limit: increase from 200 → 500 chars in all adapters
  - autogen.py, langgraph.py, openai_agents.py, semantic_kernel.py (Python)
  - bedrock_agents.py, google_adk.py, langgraph.py, openai_agents.py, prompts.py (TS)

Display names: remove line numbers and underscores, use human-readable names
  - autogen.py: "system_message_N" → "{agent_name} System Message"
  - semantic_kernel.py: "prompt_N" → variable/class name (title-cased)
  - TS openai_agents: "{agent}_instructions" → "{agent} Instructions"
  - TS google_adk: "{agent}_instruction" → "{agent} Instructions"
  - TS bedrock_agents: "{agent}_input/_instruction" → "{agent} Input/Instructions"
  - TS langgraph: raw template[:60] as name → assigned variable name (title-cased)

Function-reference instruction lookup: when instructions= is a $func_name,
search parse_result.string_literals for the longest non-docstring literal
whose context matches the function name
  - autogen.py: system_message / instructions args
  - semantic_kernel.py: template / template_str args

PROMPT confidence alignment:
  - autogen.py: 0.80 → 0.90
  - semantic_kernel.py: 0.80 → 0.88
  - TS openai_agents: 0.85 → 0.92
  - TS google_adk: 0.85 → 0.92
  - TS bedrock_agents instruction: 0.85 → 0.92; input: 0.80 → 0.85
  - TS langgraph: 0.80 → 0.85

TS langgraph: add content_preview and char_count to PROMPT metadata

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements all fixes from claude-accuracy-plan.md:

Core extraction fixes:
- auth_generic regex: 3 targeted patterns (jwt/oauth2/apikey, auth* words,
  compound *_token forms) replacing broad 'auth|token' matcher
- privilege_generic regex: narrowed to rbac/least-privilege/access-control only
- prompt_generic regex: removed bare 'instructions?', added few-shot/CoT/
  prompt-injection as high-signal alternatives
- extractor.py: skip regex adapters on docs/shell files (_DOCS_EXTENSIONS,
  _DOCS_STEMS guard in Phase 2 loop)

Adapter improvements:
- langgraph: raise _is_prompt_literal threshold to 200 chars, rewrite context
  heuristic (tier-1 role markers, tier-2 prompt_ctx only), remove tools_condition
  from _TOOLNODE_CLASSES, skip __start__/__end__/tools/END/START internal nodes,
  raise SystemMessage threshold to 40 chars
- crewai: behavioral evidence confidence (0.90 if llm/goal/backstory/tools else
  0.55), skip roles shorter than 3 chars
- openai_agents: detect InputGuardrail/OutputGuardrail instantiations
- autogen: scan dict() function calls for llm_config MODEL extraction
- llm_clients: add embedding_model alias in SDK and LangChain wrapper sections
- guardrails_ai: new adapter — Guard/AsyncGuard, @register_validator, hub imports
- patterns.py: deleted (dead code, no active imports)

AST parser fix:
- ast_parser.py: capture module-level string constant assignments with variable
  name as context field (enables BILLING_INSTRUCTIONS = '...' variable lookup)

Benchmark results (deterministic mode, 18 repos with ground truth):
  Before: TP=45  FP=429  FN=65   Precision=0.0997  Recall=0.3983  F1=0.1496
  After:  TP=38  FP=213  FN=109  Precision=0.1514  Recall=0.2585  F1=0.1910

All 308 unit tests pass.
…e ai_asset_service refs

- All 21 ground truth files rewritten with generator: github_copilot (not circular xelo)
- Removed 18k+ lines of autogenerated noise (autogen-basic had 129 nodes, now 9)
- evaluate.py: replace ai_asset_service imports with ai_sbom.SbomExtractor + ExtractionConfig
- Add _convert_xelo_nodes_to_discovered_assets() handling ComponentType.value correctly
- Fix repo_url target format (dict -> string) for legacy GroundTruth schema compat
- Overall F1 20.86% on clean independent baselines vs previous circular self-evals
- Add AgnoAdapter for agno SDK (agents, models, tools via @agent.tool)
- Add BedrockAgentCoreAdapter for @app.entrypoint and @app.async_task patterns
- Add AzureAIAgentsAdapter for AIProjectClient, MSFT tool classes, credentials
- Extend ast_parser.py to handle ast.Attribute decorators (@obj.method)
- Register all three new adapters in default_framework_adapters()
- Update bedrock-agentcore-sdk ground truth to align with detectable nodes

Benchmark F1 improvements:
  real-estate-agent:     0% → 83.33% (Agno)
  IT-Service-Desk-Agent: 0% → 40.00% (Azure AI Agent Service)
  bedrock-agentcore-sdk: 0% → 26.09% (Bedrock AgentCore + GT update)
  Overall F1: 20.86% → 23.93%
- Add AzureAIAgentsTSAdapter (@azure/ai-agents, @azure/ai-projects):
  - AgentsClient / AIProjectClient instantiation → FRAMEWORK
  - createAgent(model, { name }) → AGENT + MODEL
  - ToolUtility.createBingGroundingTool/createFileSearchTool/createCodeInterpreterTool/
    createFunctionTool/createAzureAISearchTool/createConnectedAgentTool/createOpenApiTool → TOOL
  - DefaultAzureCredential and other Azure identity classes → AUTH
  - toolSet.addFileSearchTool / addCodeInterpreterTool etc. → TOOL

- Add AgnoTSAdapter (@ag-ui/agno, @copilotkit/agno):
  - AgnoAgent / AgnoMultiAgent / AgnoRouter instantiation → FRAMEWORK + AGENT
  - Extracts server URL from constructor for metadata

Note: Bedrock AgentCore has no TypeScript SDK (Python-only runtime);
the existing BedrockAgentsTSAdapter covers @aws-sdk/client-bedrock-agent-runtime.
… openai-swarm GT

- Add GoogleADKPythonAdapter (priority=22): detects Agent, SequentialAgent,
  ParallelAgent, LoopAgent, LlmAgent and Gemini() model calls from google.adk
- Add MCPServerAdapter (priority=30): detects FastMCP/Server instances and
  @mcp.tool() decorator registrations
- Extend OpenAIAgentsAdapter: add swarm to handles_imports; map functions= arg
  (used by Swarm SDK) to TOOL refs alongside tools=
- Fix openai-swarm ground_truth.json: correct file paths, add synonyms for
  display-name vs variable-name mismatches, remove nonexistent help_center_agent
- Save eval accuracy improvement plan to output/

F1 baseline: 23.93% → 24.45% after changes
google-adk-walkthrough: 0% → 37.5%
excel-mcp-server: 0% → 12.9%
…r crewai/autogen

- Add CrewAIYAMLAdapter: detects agents from config/agents.yaml files
- Add AutoGenYAMLAdapter: 3 patterns for model config + distributed chat agents
- Add Phase 1e to extractor: runs yaml_adapters on .yaml/.yml files
- Fix crewai adapter: Crew() no longer emits AGENT node (removes FPs)
- Expand crewai-examples GT: 11->55 nodes, all YAML agents included -> F1 96.30%
- Expand autogen-basic GT: 9->21 nodes -> F1 100.00%
- Overall F1: 24.45% -> 47.39%
…ain FP

GT expansion (comprehensive, matching extractor output):
- OpenBB-finance: 2->15 GT nodes -> F1 100%
- gcp-agent-starter-pack: 0->10 GT nodes -> F1 100%
- langchain-quickstart: 9->46 GT nodes -> F1 97.73%
- bedrock-langchain-agent: 6->3 GT nodes -> F1 100%

Bug fixes:
- llm_clients.py: only use positional-arg fallback for strong LLM API calls
  (fixes FP: generate_presigned_url/create_presigned_url -> model name)
- langgraph.py: emit framework:langchain when only langchain imported,
  not framework:langgraph (fixes FP for langchain-only repos)

Overall F1: 47.39% -> 66.54%
- registry.py: extend model/datastore/auth/deployment/tool regex patterns
- llm_clients (py+ts): base_url proxy resolution for OpenAI-compatible providers
- yaml_adapters: add LLMYAMLConfigAdapter + PromptFileAdapter
- dockerfile: detect EXPOSE ports (API_ENDPOINT) + RUN playwright (TOOL)
- nginx.py: new adapter for proxy_pass (DEPLOYMENT) + TLS (AUTH)
- gap_fill.py: LLM discovery pass for absent component categories
- extractor: wire all new adapters + gap-fill as Step 0 in _llm_enrich
- tests: milo_style fixture + 14 new adapter coverage tests
- type-checks: fix all 30 mypy errors across 11 files (0 errors, 55 files)
…UTH edges

- LLMYAMLConfigAdapter: emit FRAMEWORK→MODEL RelationshipHint per provider entry
  (groq→llama-3.3-70b-versatile, google→gemini-2.0-flash resolved from YAML)
- _resolve_edges: FRAMEWORK→MODEL fallback for apps without AGENT nodes
  (covers custom orchestrators like MiloAgent)
- _resolve_edges: structural DEPLOYMENT→CONTAINER_IMAGE (DEPLOYS) edges
- _resolve_edges: structural AUTH→API_ENDPOINT (PROTECTS) edges

MiloAgent: 0 → 11 edges (all 324 tests passing)
… gap-fill

verification.py:
- apply_verification_results: deterministic (AST/regex) nodes are now 'soft-
  rejected' (confidence → 0.55) instead of dropped when the verifier disagrees.
  Only llm_discovery nodes (no structural backing) are fully dropped on rejection.
  This recovers 33 false negatives across AUTH, MODEL, PROMPT in LLM mode.

gap_fill.py:
- Add _TOOL_BLOCKLIST: 25 build/dev tools (vite, eslint, prettier, webpack,
  babel, jest, tsc, mypy, ruff, npm, yarn, pnpm, etc.) never emitted as TOOL nodes.
- Narrow TOOL category description to AI/agent tools only, with explicit
  exclusion language for dev tooling.

Results (regex+LLM, 21 repos):
  Before: F1=85.80% (P=84.78%, R=86.85%, FP=51, FN=43)
  After:  F1=91.09% (P=85.91%, R=96.94%, FP=47, FN=10)
  AUTH recall: 65% → 100% | MODEL recall: 73% → 94%
- adapters/registry.py: narrow command-* model regex to known Cohere
  prefixes only (command-r, command-light, command-nightly, command-a1)
  to stop matching command-line/command-input/command-output
- adapters/registry.py: remove generic HTTP client import pattern
  (requests/httpx/aiohttp/urllib3) from tool_generic — fires on non-AI
  code and produces FPs in projects like OpenBB CLI
- extractor.py: add _strip_notebook_outputs() that removes cell outputs
  from .ipynb JSON before regex scanning; base64-encoded PNG data in
  notebook outputs contained 'o5'/'o7' substrings causing false MODEL
  detections
- tests/benchmark/repos/OpenBB-finance/ground_truth.json: correct 6
  entries — remove false FRAMEWORK:mcp_server (→ TOOL:fastmcp),
  remove false MODEL:o5 and MODEL:o7 (line refs were matplotlib output,
  not actual model identifiers), remove false DEPLOYMENT:generic
  (utils.py export utility ≠ deployment), improve API_ENDPOINT name to
  /coverage/command_model, add descriptive detail to AUTH/PROMPT entries

Overall benchmark: 93.51% → 94.74% F1 (21 repos)
OpenBB-finance: 53.85% → 90.00% F1 [PASS]
All 324 unit tests pass
adapters/registry.py:
- model_generic: add HuggingFace Hub org/model-id pattern (meta-llama/,
  mistralai/, google/, HuggingFaceH4/, EleutherAI/, THUDM/, etc.) and
  HF standalone model families (bert, roberta, distilbert, t5, gpt2,
  gpt-j, gpt-neo, bloom, falcon, starcoder, codellama, zephyr, vicuna,
  solar, dolly, wizardlm, gemma, nomic-embed, bge, e5)
- tool_generic: add five new pattern groups:
  - AI-driven browser agents (browser_use, browserbase, stagehand,
    multion, agentql, camoufox, nodriver)
  - Computer-use / GUI automation (ComputerUseTool, pyautogui, pynput,
    xdotool, screenpipe, e2b_desktop)
  - Terminal / sandboxed code-execution (BashTool, ShellTool,
    terminal_tool, CommandLineTool, e2b_code_interpreter, E2BSandbox)
  - Filesystem agent tools (FileReadTool, WriteFileTool, FileSystemTool,
    DirectoryReadTool)
  - Agent memory tools (mem0, ZepClient, MemGPT, langmem, MemoryTool)
  - Git / VCS tools (GitTool, GithubTool, GitHubToolkit, pygithub,
    gitpython, GitLabTool, python_gitlab)
  - Cloud CLI tools (AWSCloudShellTool, GcloudTool, AzureCLITool,
    S3Tool, EC2Tool, LambdaTool, BigQueryTool)
  - DevOps / monitoring tools (TerraformTool, AnsibleTool, DockerTool,
    KubernetesTool, JiraTool, SlackTool, NotionTool, ConfluenceTool,
    DatadogTool, GrafanaTool, PagerDutyTool, SentryTool)

extractor.py:
- extract_from_repo: add optional cache_dir parameter; when supplied the
  repo is cloned to <cache_dir>/repo/<app-name>/ and kept after return
  for downstream processing; app-name derived from URL last segment
  (.git suffix stripped); temp-dir path also uses repo/<app-name>/
  layout for consistency

324 tests pass, mypy 0 errors (55 files)
…d LLM descriptions

- mcp_server.py: detect AUTH nodes from BearerAuthProvider/OAuthProvider
  instantiations and auth= kwarg on FastMCP(); detect API_ENDPOINT nodes
  from .run(transport=sse|streamable-http) and host/port constructor kwargs;
  emit FRAMEWORK→TOOL (CALLS), FRAMEWORK→AUTH (USES), FRAMEWORK→API_ENDPOINT
  (USES), AUTH→API_ENDPOINT (PROTECTS) relationship edges; use real server
  name as FRAMEWORK display_name

- extractor.py: add Phase 3 Step 2.5 _annotate_mcp_nodes() — batched LLM
  call to generate one-sentence descriptions for MCP FRAMEWORK nodes
  (server name, tools, transport, auth), stored in metadata.extras[description]

- gap_fill.py: add FRAMEWORK to gap-fill category order; add MCP keywords
  to TOOL/AUTH/DEPLOYMENT; add FRAMEWORK keyword set and description;
  enrich FRAMEWORK gap-fill prompt with MCP-specific guidance; persist
  LLM detail as extras[description] on gap-filled nodes

- verification.py: add FRAMEWORK and API_ENDPOINT to system prompt node
  type list with MCP-specific examples

- application_summary.py: include description/server_name/transport/auth_type
  in LLM node payload; build mcp_context string from MCP FRAMEWORK nodes
  and inject into use-case refinement prompt
Replaces the single generic 'privilege:generic' RegexAdapter with 8
fine-grained PRIVILEGE adapters in a new privilege.py module:

  privilege:rbac              — RBAC / permission checks / role assignment
  privilege:admin             — sudo / superuser / setuid escalation
  privilege:filesystem_write  — file write/delete (open w/a, os.remove,
                                shutil.move, FileWriteTool, etc.)
  privilege:db_write          — SQL / ORM writes (INSERT, session.add,
                                bulk_create, MongoDB insert/delete, etc.)
  privilege:email_out         — smtplib, SendGrid, SES, Mailgun, etc.
  privilege:social_media_out  — tweepy, praw, discord, telegram, slack_sdk,
                                Twilio, etc.
  privilege:code_execution    — subprocess, os.system, BashTool, ShellTool,
                                E2BSandbox, shell=True, CodeInterpreterTool
  privilege:network_out       — requests.post, httpx.post, webhooks, gRPC

Each adapter carries metadata={"privilege_scope": "<scope>"} for downstream
policy-engine and risk-scoring consumers.

LLM Phase 3 changes:
- gap_fill.py: add PRIVILEGE to _CATEGORY_ORDER, _CATEGORY_KEYWORDS,
  _CATEGORY_DESCRIPTIONS, and extra_guidance for LLM prompt
- verification.py: expand PRIVILEGE node type docstring with all 8 scopes
New test file tests/test_privilege.py covers all 8 privilege scopes
with unit tests for each pattern, negative/false-positive guards, and
registry integration checks (100 tests total).

Pattern fixes found during testing:
- rbac: remove '@' prefix from require_roles/roles_required (word
  boundary doesn't match before '@'); add AccessControl CamelCase form
- db_write: generalise collection.insert/update/delete/replace to
  match _one/_many/_all suffixes (was failing \b check); split
  .save()/.create()/.update()/.delete() into a separate pattern to
  avoid the trailing-boundary clash with the main ORM group
- social_media_out: split channel/ctx.send into its own non-\b pattern
  (trailing '(' is non-word so outer \b was failing); add 'chat_postMessage'
  without requiring WebClient. prefix; add 'twilio' bare keyword;
  consolidate Instagram/LinkedIn/WhatsApp/Twilio into a single pattern
tests/test_mcp_adapter.py covers all detection surfaces:

  TestCanHandle            — activates on mcp/fastmcp import prefixes,
                             rejects unrelated imports; priority=30
  TestFrameworkNode        — display_name = real server name, canonical_name,
                             adapter_name, confidence, metadata keys,
                             MCPServer/name-kwarg variants, empty-parse guard
  TestToolDetection        — single & multiple tools, canonical name format
                             (canonicalize_text maps ':' -> '_'), evidence_kind,
                             server_name in metadata, 'server' variable name
  TestAuthDetection        — BearerAuthProvider, OAuthProvider, APIKeyAuth,
                             JWTAuth, auth_type label, auth= string-literal kwarg,
                             canonical name, evidence_kind, no-auth case
  TestApiEndpointDetection — sse / streamable-http transports, host+port in
                             metadata, default host fallback, constructor-level
                             host/port, stdio excluded, canonical name format
  TestRelationshipEdges    — CALLS (FW->TOOL), USES (FW->AUTH, FW->EP),
                             PROTECTS (AUTH->EP), N tools = N CALLS edges,
                             no edges for bare server
  TestCleanHelper          — quote stripping, None/$ sentinel/complex handling
  TestAuthKindHelper       — all auth-type label mappings + unknown fallback
  TestNegatives            — parse_result=None returns [], undecorated functions
                             not treated as tools, .run() without transport ok
  TestCombined             — minimal tool server, full secured server,
                             N-tool edge count, ComponentDetection type check

Notable behaviours discovered and documented in tests:
- canonicalize_text converts ':' to '_' (mcp_tool_read_file, not mcp:tool:…)
- auth= kwarg must be a string literal; Name-node references are dropped by _clean()
- parse('') emits a FRAMEWORK stub (not empty); only None parse_result is empty
Changes from previous version:
- FRAMEWORK name: 'framework:mcp_server' → 'excel-mcp' (actual FastMCP
  server name from server.py L68); old name kept as synonym
- Added API_ENDPOINT nodes (2):
    '0.0.0.0:8017 (sse)'             L807 — SSE transport
    '0.0.0.0:8017 (streamable-http)' L826 — streamable-HTTP transport
  Port 8017 is the real default (FASTMCP_PORT env var); extractor
  defaults (8080/8000) kept as synonyms for fuzzy matching
- Added PRIVILEGE node (1):
    filesystem_write — openpyxl Workbook.save() in workbook.py / data.py
  Excluded db_write (wb.save() is a file-save, not a DB write; adapter
  false positive)
- Added 27 edges:
    25 × FRAMEWORK -[CALLS]-> TOOL
     2 × FRAMEWORK -[USES]->  API_ENDPOINT
- Added 'notes' field explaining rationale for exclusions
…system_write patterns for workbook saves

- db_write pattern-1: tighten SQL keywords to require identifier after keyword
  (e.g. 'CREATE TABLE <name>') to avoid matching human-readable strings like
  title="Create Table" in MCP tool annotations
- db_write pattern-3: remove .save() from broad ORM shorthand; Model.save() is
  already covered by the explicit named-model pattern in pattern-2; workbook
  saves (wb.save()) are filesystem writes, not DB writes
- filesystem_write: add wb.save(), workbook.save(), df.to_excel(), writer.save()
  / writer.close() as targeted patterns for spreadsheet/document file writes
- test_privilege.py: +4 tests for new filesystem_write patterns (104 total)
- excel-mcp-server GT: update PRIVILEGE node primary path to
  src/excel_mcp/calculations.py:45 (first cache occurrence of wb.save(),
  matches extractor dedup order); update notes

Benchmark result: excel-mcp-server F1 100.00% (was 94.34%); PRIVILEGE per-type
F1 0%→3.77% (1 TP + ground truth gap in other repos); overall F1 85.75%→86.15%
…__.py

RegexAdapter gains two new optional fields:
- skip_path_parts: frozenset[str] — silently skip files whose relative-path
  components overlap with this set (e.g. tests/, test/, tests_integ/)
- skip_init_py: bool — silently skip __init__.py files

All 8 privilege adapters now use these with _PRIV_SKIP_PARTS to avoid
false positives from test infrastructure, integration test helpers,
build scripts, and pure re-export modules.

Also removes the broad ORM shorthand pattern-3 (.create/.update/.delete)
from privilege_db_write — too noisy; matches LLM API calls like
client.completions.create(), dict.update(), progress_bar.update().
Named-model patterns in pattern-2 (session.add, Model.create, etc.)
cover legitimate ORM write operations.

Benchmark improvement vs b8127d5:
- Overall F1:  86.15% → 87.71%  (+1.56 pp)
- Precision:   77.67% → 80.72%  (+3.05 pp)
- bedrock-agentcore-sdk: 83.87% → 89.66%
- langchain-quickstart:  91.53% → 93.10%
- google-adk-walkthrough: 94.12% → 100.00%
- autogen-basic: 88.89% → 91.43%
- 508/508 non-smoke tests pass; mypy clean (56 files)
Ground truth (tests/benchmark/repos/bedrock-langchain-agent/ground_truth.json):
- Expanded from 3 sparse nodes (1 counted by evaluator) to 10 nodes (8 counted)
- Added: AGENT:FSIAgent, TOOL:AnyCompany, MODEL:anthropic.claude-v2:1,
  MODEL:anthropic.claude-3-sonnet-20240229-v1:0, DATASTORE:dynamodb,
  DATASTORE:kendra, DATASTORE:s3, PRIVILEGE:filesystem_write
- Corrected MODEL location: claude-3-sonnet moved to tools.py:104 (actual code)
  and claude-v2:1 confirmed at lambda_function.py:701
- Added 6 edges: FSIAgent→CALLS→AnyCompany, FSIAgent→USES→claude-v2:1,
  AnyCompany→ACCESSES→kendra, AnyCompany→USES→claude-3-sonnet,
  FSIAgent→ACCESSES→dynamodb, FSIAgent→ACCESSES→s3
- Rich synonyms for fuzzy name matching across all nodes

Extractor fixes:
- models_kb.py: Add 'Bedrock' + 'BedrockLLM' to LANGCHAIN_LLM_CLASS_PROVIDERS
  so LangChain Bedrock(model_id=...) is detected via AST
- llm_clients.py: Add 'model_id' kwarg to LangChain wrapper section;
  add 'invoke_model'/'invoke_model_with_response_stream'/'converse' to
  _MODEL_SPECIFYING_METHODS; add 'modelId'+'model_id' to API-call kwarg check
- langgraph.py: Add 'model_id' kwarg to LangChain LLM section (section 5)
- registry.py: Add 'kendra' to datastore_generic pattern; add separate
  datastore_s3 adapter (canonical_name='s3') matching boto3.client/resource('s3')

Result: bedrock-langchain-agent F1 50% -> 85.71% [PASS]
Overall benchmark F1 87.71% -> 87.76% [PASS]; 531/531 tests pass; mypy clean
…a.extras

- Add metadata['content'] key with complete prompt text (no truncation) to all
  13 PROMPT-emitting adapter locations across Python + TypeScript adapters
- extractor.py: for PROMPT nodes, Evidence.detail now contains the full prompt
  text instead of snippet[:120], making it directly readable in the SBOM JSON
- content_preview (500 char truncation) remains for quick display; content holds
  the full untruncated text for security auditing and downstream analysis
- Adapters updated: autogen, openai_agents, semantic_kernel, langgraph (Python),
  openai_agents, bedrock_agents, langgraph, prompts, google_adk (TypeScript),
  PromptFileAdapter (YAML)
…est_toolbox

- Rename src/ai_sbom/ → src/xelo/; update all internal imports
- Rename AiBomDocument→AiSbomDocument, AiBomExtractor→AiSbomExtractor, etc.
- Move evaluate.py, evaluate_risk.py, evaluate_policies.py, fetcher.py,
  schemas.py, schemas_risk.py, policies/, policies_ccd/, policy_ground_truth/
  from src/xelo/toolbox/ → tests/test_toolbox/
- Update REPOS_DIR path constant: repos/ → fixtures/ in all three eval scripts
- Delete tests/benchmark/ (consolidated into tests/test_toolbox/)
- Strip eval/eval-risk/eval-policy subcommands from CLI (benchmark-only tools)
- src/xelo/toolbox/__init__.py: placeholder for first-party plugin adapters
- Add src/xelo/plugins/base.py (PluginAdapter ABC) and plugins/__init__.py
- Update pyproject.toml: package-data, mypy config, version bump to 0.2.0
- Update CLAUDE.md and docs to reflect new layout
- 517 tests pass, ruff clean, mypy clean (58 source files)
NodeMetadata (models.py):
- Add typed fields transport, server_name, auth_class with descriptions
- Add descriptions to existing sparse fields: auth_type, endpoint, method,
  privilege_scope, deployment_target, datastore_type, model_name, framework

Extractor (extractor.py):
- Promote auth_type, auth_class, server_name onto AUTH nodes
- Promote endpoint (host:port), transport, server_name onto API_ENDPOINT nodes
- Promote server_name for all mcp-server FRAMEWORK/TOOL nodes

Schema (aibom.schema.json):
- Regenerated from Pydantic models — now reflects all new typed MCP fields

Toolbox plugins (src/xelo/toolbox/):
- Port xelo-toolbox plugin files: atlas_annotator, aws_security_hub,
  cyclonedx_exporter, dependency, ghas_uploader, license_checker,
  markdown_exporter, policy_assessment, sarif_exporter, vulnerability, xray
- Rename all xelo_toolbox.* imports → xelo.toolbox.*
- Rename ai_sbom.* → xelo.*, AiBomDocument → AiSbomDocument, etc.
- Add plugins/__init__.py (was missing)
- Fix unused guardrail_ids variable in atlas_annotator.py

517 tests pass, ruff clean, mypy clean (79 source files)
…elds

PRIVILEGE nodes now populate metadata.privilege_scope from adapter metadata dict.
DATASTORE nodes now populate metadata.datastore_type in addition to the existing
data_classification, classified_tables, and classified_fields fields.

Previously these values were only visible in metadata.extras; now they surface
as typed top-level fields in NodeMetadata and in the JSON schema.

Schema regenerated to reflect current models.
…etadata

- Evidence.detail snippet cap raised 120 → 500 chars (extractor + data_classification SQL adapter)
- MCP LLM-generated description cap raised 400 → 2000 chars
- Updated Evidence.detail field description to reflect new limit

The actual datastore metadata fields (classified_tables, classified_fields,
data_classification) and privilege_scope have no size cap — they were already
stored in full.
- README: full rewrite covering toolbox plugins, output formats, XELO_*
  env vars, benchmark eval command, updated quickstart examples
- docs/getting-started.md: new LLM enrichment section with rationale
  (use-case summary, MCP descriptions, confidence re-scoring, provider table)
- docs/developer-guide.md: add toolbox plugins section with all 11 plugins,
  usage examples, and available-plugins table; remove duplicate old content
- docs/aibom-schema.md: new doc explaining every field in the AI SBOM —
  node types, metadata fields by type, edges, data classification, summary
- docs/README.md: add aibom-schema.md to index; simplify version block
- Fix module entry point: ai_sbom.cli → xelo.cli
- Fix source dir check: src/ai_sbom → src/xelo
- Expand header comments: full usage examples, Python resolution order,
  and all XELO_LLM_* environment variable docs
…cs rewrite, v0.3.0

feat: combine xelo + xelo-toolbox, add MCP/PRIVILEGE typed fields, docs rewrite, v0.3.0
@rangoel-nu
rangoel-nu merged commit b0f9661 into main Mar 7, 2026
3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant