feat(extract): recognize .zsh and add SAS (.sas) extraction - #2835
feat(extract): recognize .zsh and add SAS (.sas) extraction#2835SomSamantray wants to merge 6 commits into
Conversation
…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>
There was a problem hiding this comment.
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 eagerly —
graphify/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 extractor —
graphify/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 extractor —
graphify/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 files —
graphify/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).
Response to Graphify advisory findingsThe four advisory findings were verified against the actual code and the real 1-3. "Eager import of optional SAS extractor" (extract.py:52, extract.py:56, extractors/init.py:32)The grammar import is deferred inside 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 This matches 4. "SAS child node IDs omit the file path and can collide across same-stem files" (sas.py:82)
6 nodes extracted, 6 unique IDs, zero collisions. Inline coupling note (extract_sas fan-out)The 8 callees are the extractors own helpers ( Summary
Happy to address any concrete, reproducible concern with a specific failing case. |
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
There was a problem hiding this comment.
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 time —
graphify/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 time —
graphify/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: |
There was a problem hiding this comment.
extract_sas()
fans out to 8 callees (efferent coupling); 12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
.zshscripts 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..sasfiles get first-class structural extraction:data/procsteps and%macrodefinitions become nodes, and%macrocall 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
sasoptional extra so default installs are unaffected.Fixes #2825, Fixes #2681.
Validation
test_skillgenfailures that require full git history unavailable in a shallow clone)..zshclassification + dispatch + extraction;.sasclassification + 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.ruffandpyrightclean on all changed files.tree-sitter-sas0.4.2 grammar, including the nested in-step macro call case.