Skip to content

feat(gs): extract Google Apps Script as JavaScript - #3505

Open
fridaybyjarvis wants to merge 1 commit into
Graphify-Labs:v8from
fridaybyjarvis:feat/gs-apps-script
Open

feat(gs): extract Google Apps Script as JavaScript#3505
fridaybyjarvis wants to merge 1 commit into
Graphify-Labs:v8from
fridaybyjarvis:feat/gs-apps-script

Conversation

@fridaybyjarvis

Copy link
Copy Markdown

What

Routes .gs (Google Apps Script) to the JavaScript extractor.

Apps Script sources are plain JavaScript saved with a .gs extension — that is what clasp pulls down, and what every Apps Script project in a repo looks like. The extension was missing from CODE_EXTENSIONS, the extractor _DISPATCH, and the JS language-family maps, so an Apps Script project was classified as non-code and contributed nothing to the graph.

Why

I ran graphify over a 29-file Apps Script project (school-district volunteer management, all logic in .gs). Before:

83 nodes · 84 edges · 9 communities

Every node came from docs/RUNBOOK.md, README.md, the two appsscript.json manifests, and one .js test file. Every god node was a markdown heading. The report's "Surprising Connections" section read None detected - all connections are within the same source files, which was true only because the code had been skipped.

After:

285 nodes · 835 edges · 14 communities

God nodes are now the actual shared helpers — readAll_() at 46 edges, requireRole_() at 40, withLock_() at 21.

The .gs ambiguity

.gs is also used by GLSL geometry shaders and by Gosu, so dispatch goes through _is_apps_script, which withholds the extractor when a GLSL or Gosu marker is present rather than force-parsing the file through the JS grammar. That leaves the file surfaced by the existing no-AST-extractor warning, same outcome as MATLAB .m.

The sniff runs the opposite direction from _is_objc_source. .m is guilty until proven innocent because Objective-C and MATLAB are both common and ObjC has a reliable positive marker (@implementation, #import). .gs has no comparable rival, and Apps Script has no usable positive marker: a pure-logic helper file calls no SpreadsheetApp or DriveApp service at all. In the project above, 8 of 29 .gs files touch no service global, so a positive test would have dropped exactly the shared-helper files that turned out to be the god nodes.

Extension sets touched

Location Change
detect.CODE_EXTENSIONS .gs is code
extract._DISPATCH .gs -> extract_js
extract._LANG_FAMILY_BY_EXT .gs -> jsts
extract LanguageResolver typescript_member_calls member-call resolution for Obj.method()
extract._get_extractor GLSL/Gosu guard
build._EDGE_LANG_FAMILY .gs -> js
analyze._LANG_FAMILY .gs -> js
extractors.models._JS_CACHE_BYPASS_SUFFIXES .gs
cli._HOOK_SOURCE_EXTS rebuild hook fires on .gs edits

_JS_TS_CALL_SUFFIXES is deliberately left alone. That gate drops cross-file calls with no import evidence, which is right for ES modules and wrong for Apps Script: .gs files share one global scope and have no imports, so every cross-file call is real. Adding .gs there would have suppressed most of the 835 edges.

_JS_RESOLVE_EXTS / _JS_INDEX_FILES are also left alone for the same reason — there are no module specifiers to resolve.

Tests

tests/test_gs_apps_script.py, modeled on tests/test_cjs_module_extension.py:

  • regression locks on each extension set
  • .gs parses to the same node set as the identical source saved as .js
  • a .gs file with no Apps Script service global still routes to extract_js
  • a GLSL geometry shader and a Gosu class both route to None

Full suite: 5445 passed, 95 skipped. The 4 failures in tests/test_ollama_retry_cap.py are ModuleNotFoundError: No module named 'openai' and reproduce on unmodified v8 in the same environment.

Apps Script sources are plain JavaScript saved as `.gs` — what `clasp` pulls
down and what every Apps Script project in a repo looks like. `.gs` was absent
from `CODE_EXTENSIONS`, the extractor `_DISPATCH`, and the JS language-family
maps, so an entire Apps Script project was classified as non-code and
contributed nothing: a build over a repo whose logic lives in `.gs` returned
only its markdown and `appsscript.json` manifests. On a 29-file Apps Script
project this was 83 nodes / 84 edges before and 285 / 835 after, with every
god node before the change coming from a doc.

`.gs` is not exclusive to Apps Script — GLSL geometry shaders and Gosu use it
too — so routing goes through `_is_apps_script`, which withholds the extractor
when a GLSL or Gosu marker is present rather than force-parsing the file into
garbage. The sniff runs the opposite way from the `.m` Objective-C/MATLAB
split: `.m` has a genuine rival and is guilty until proven innocent, while
`.gs` in a repo is nearly always Apps Script and its own markers are unusable
as a positive test (a pure-logic `.gs` helper calls no `SpreadsheetApp` or
`DriveApp` service at all).

`_JS_TS_CALL_SUFFIXES` deliberately does not gain `.gs`: that gate drops
cross-file calls lacking import evidence, which is right for ES modules and
wrong for Apps Script, where every file shares one global scope and imports do
not exist.

@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 1 advisory finding(s) below merit a look before merge.

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


Graphify review — findings

Adds .gs (Google Apps Script) as a first-class JavaScript-family extension across detection, extraction, edge/language-family maps, and hook source filtering, so Apps Script projects are indexed as code instead of being dropped as non-code. Routes .gs through extract_js and the TypeScript member-call resolver via a new _is_apps_script sniff that withholds the extractor only when a GLSL or Gosu marker is present, leaving such files without an AST extractor (surfaced by the usual warning) rather than force-parsing them.

Worth a look

  • Unbounded .gs sniff reads entire file per workergraphify/extract.py:6085 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 3483 functions depend on the 584 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 585 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: build_from_json() — 204 callers, 19 callees
  • new: detect() — 110 callers, 15 callees
  • new: build_merge() — 68 callers, 14 callees
  • new: to_obsidian() — 38 callers, 14 callees
  • new: _extract_generic() — 18 callers, 26 callees
  • new: save_manifest() — 40 callers, 11 callees
  • …and 98 more — each is listed as a finding

Verification — 3483 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: 3229 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

269 of 269 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — impact, full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — impact, full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — impact, full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — impact, full-run-safety
  • tests/test_benchmark_raw_graph.py — impact, full-run-safety
  • tests/test_build.py — impact, full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — impact, full-run-safety
  • tests/test_build_merge_shrink_guard.py — impact, full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — impact, full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — impact, full-run-safety
  • tests/test_chunking.py — impact, full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — impact, full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — impact, full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — impact, full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — impact, full-run-safety
  • tests/test_confidence.py — impact, full-run-safety
  • tests/test_corrupt_graph_json.py — impact, full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • tests/test_csharp_enum_members.py — impact, full-run-safety
  • tests/test_csharp_field_generic_args.py — impact, full-run-safety
  • … and 219 more

non-code file(s) changed (README.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (README.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_get\_extractor.

The verifier did not have enough to check \_get\_extractor, 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; 105 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
return not any(marker in head for marker in _NON_APPS_SCRIPT_GS_MARKERS)


def _get_extractor(path: Path) -> Any | None:

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 regression_get_extractor()

fans out to 7 callees (efferent coupling); 31 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.

1 participant