Skip to content

feat(extract): recognize .zsh and add SAS (.sas) extraction - #2835

Open
SomSamantray wants to merge 6 commits into
Graphify-Labs:v8from
SomSamantray:feat/zsh-sas-extraction
Open

feat(extract): recognize .zsh and add SAS (.sas) extraction#2835
SomSamantray wants to merge 6 commits into
Graphify-Labs:v8from
SomSamantray:feat/zsh-sas-extraction

Conversation

@SomSamantray

Copy link
Copy Markdown

Summary

.zsh scripts are now recognized as shell code and routed through the existing Bash AST extractor, so zsh files contribute functions, calls, and source edges instead of being silently skipped. .sas files get first-class structural extraction: data/proc steps and %macro definitions become nodes, and %macro call sites resolve to their definitions — including macros invoked inside steps and case-variant names, which SAS treats as equivalent.

SAS extraction is deterministic and local (tree-sitter, no LLM), matching the pattern used by the other niche-language extractors. It ships behind a sas optional extra so default installs are unaffected.

Fixes #2825, Fixes #2681.

Validation

  • 4,637 tests pass (full suite minus pre-existing test_skillgen failures that require full git history unavailable in a shallow clone).
  • New coverage: .zsh classification + dispatch + extraction; .sas classification + dispatch, step/macro nodes, defines and calls edges, multi-step files, same-line steps, case-insensitive macro resolution, duplicate-macro dedup, and the missing-dependency error path.
  • ruff and pyright clean on all changed files.
  • Node/edge behavior verified against the real tree-sitter-sas 0.4.2 grammar, including the nested in-step macro call case.

SomSamantray and others added 5 commits August 18, 2026 00:52
…hify-Labs#2825)

.zsh was missing from CODE_EXTENSIONS and the shell dispatch table, so
suffixed zsh scripts were silently unclassified even though the shebang
path already mapped zsh to extract_bash. Add .zsh to the extension
tables and the bash source-edge filter, plus a fixture and dispatch test.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
SAS files were not classified at all, so they were invisible to detect()
and contributed nothing. Add a dedicated tree-sitter SAS extractor that
emits file, data/proc step, and %macro nodes plus same-file macro call
edges, registered in LANGUAGE_EXTRACTORS and re-exported from the facade.
.sas is now in CODE_EXTENSIONS and wired through _DISPATCH with the sas
optional extra (mirroring the commonlisp/ocaml niche-language pattern).

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…ate SAS steps

Apply simplification-review findings: use the shared _read_text helper for
tree-sitter node text (matching sibling extractors), map .zsh to the shell
language family so family-gated passes see it, and disambiguate repeated
data/proc step node IDs by line so multi-step SAS files keep every step.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Address code-review findings: resolve macro calls nested inside data/proc
steps, case-fold macro names per SAS semantics, dedup defines edges,
disambiguate same-line steps by byte offset, distinguish an installed-but-
broken SAS grammar from a missing one (Graphify-Labs#2602 pattern), and drop the dead
raw_calls key. Extend bash source/invocation guards from .sh-only to the
shell-family suffixes so bare zsh sourcing resolves, and register the new
families in build.py's edge-family mirror.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds first-class SAS support (.sas via a new graphify/extractors/sas.py tree-sitter extractor wired into _DISPATCH, LANGUAGE_EXTRACTORS, and _EXTRA_FOR_EXTENSION) and routes .zsh files through the existing bash extractor. Extends CODE_EXTENSIONS, _EDGE_LANG_FAMILY, _LANG_FAMILY_BY_EXT, and the bash source-suffix matching (_SHELL_SUFFIXES) accordingly, and bumps the README grammar count/table.

Worth a look

  • Optional SAS extractor is imported eagerlygraphify/extract.py:52 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Core extract module eagerly imports optional SAS extractorgraphify/extract.py:56 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Public extractors package now eagerly requires optional SAS extractorgraphify/extractors/__init__.py:32 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • SAS child node IDs omit the file path and can collide across same-stem filesgraphify/extractors/sas.py:82 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 3067 functions depend on the 1189 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 469 callers, 41 callees
  • new: _rebuild_code() — 98 callers, 51 callees
  • new: build_from_json() — 174 callers, 18 callees
  • new: detect() — 107 callers, 15 callees
  • new: build_merge() — 46 callers, 14 callees
  • new: extract_bash() — 41 callers, 10 callees
  • new: save_manifest() — 34 callers, 11 callees
  • new: to_obsidian() — 29 callers, 12 callees
  • …and 76 more — each is listed as a finding

Verification — 3067 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2730 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_bash.

The verifier did not have enough to check extract\_bash, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 83 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extractors/sas.py
@SomSamantray

Copy link
Copy Markdown
Author

Response to Graphify advisory findings

The four advisory findings were verified against the actual code and the real tree-sitter-sas 0.4.2 grammar. All four are false positives — reproduced below with evidence.

1-3. "Eager import of optional SAS extractor" (extract.py:52, extract.py:56, extractors/init.py:32)

The grammar import is deferred inside extract_sas(), exactly like the other optional-dependency extractors:

def extract_sas(path: Path) -> dict:
    try:
        import tree_sitter_sas as tssas
        from tree_sitter import Language, Parser
    except ImportError as e:
        ...

Verified empirically with tree_sitter_sas removed:

$ python3 -c "import sys; sys.modules[tree_sitter_sas]=None; import graphify; import graphify.extract; from graphify.extractors import LANGUAGE_EXTRACTORS"
import OK — graphify imports fine with tree_sitter_sas absent

This matches sql.py, terraform.py, commonlisp.py, and ocaml.py, which all defer their grammar import inside the extractor function. The module-level from graphify.extractors.sas import extract_sas only imports the function object, not the grammar.

4. "SAS child node IDs omit the file path and can collide across same-stem files" (sas.py:82)

_file_stem preserves the full path (the #1504 fix), so same-stem files in different directories get distinct IDs. Verified through the real extract() pipeline (which runs the id-remap pass):

pkg_a_settings_greet   <- pkg_a/settings.sas
pkg_b_settings_greet   <- pkg_b/settings.sas

6 nodes extracted, 6 unique IDs, zero collisions.

Inline coupling note (extract_sas fan-out)

The 8 callees are the extractors own helpers (add_node, add_edge, _emit_macro_calls, _step_label), identical in shape to sibling extractors like extract_sql. This is the standard extractor structure, not a defect.

Summary

  • Required CI: Graphify check passed.
  • Graphify Formal Verification: honest abstention ("could not verify"), not a failure.
  • No code changes made — the findings do not identify real defects, and changing code to satisfy unverified advisory claims would be incorrect.

Happy to address any concrete, reproducible concern with a specific failing case.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds a SAS extractor (graphify/extractors/sas.py, wired into _DISPATCH, LANGUAGE_EXTRACTORS, and _EXTRA_FOR_EXTENSION) that emits data/proc steps, %macro definitions, and macro-call edges for .sas files. Routes .zsh through the existing Bash extractor across detect, extract, and build, and generalizes the bash source/invocation matcher to a _SHELL_SUFFIXES tuple (.sh/.bash/.zsh/.ksh). Updates the README grammar count and extension list accordingly.

Worth a look

  • Core extract module now requires optional SAS dependency at import timegraphify/extract.py:55 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Extractors package now requires optional SAS dependency at import timegraphify/extractors/__init__.py:31 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 3067 functions depend on the 1189 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 469 callers, 41 callees
  • new: _rebuild_code() — 98 callers, 51 callees
  • new: build_from_json() — 174 callers, 18 callees
  • new: detect() — 107 callers, 15 callees
  • new: build_merge() — 46 callers, 14 callees
  • new: extract_bash() — 41 callers, 10 callees
  • new: save_manifest() — 34 callers, 11 callees
  • new: to_obsidian() — 29 callers, 12 callees
  • …and 76 more — each is listed as a finding

Verification — 3067 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2730 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_bash.

The verifier did not have enough to check extract\_bash, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 83 more finding(s) on lines outside this diff (see the check run).

from graphify.extractors.base import _file_stem, _make_id, _read_text


def extract_sas(path: Path) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionextract_sas()

fans out to 8 callees (efferent coupling); 12 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recognize .zsh files in shell AST extraction Add support for SAS files (.sas) in graphify

1 participant