Skip to content

fix(jcl): recognize PARM= on continuation lines, bound Mode A args search - #2518

Merged
squid-protocol merged 3 commits into
mainfrom
fix/jcl-2482-args-continuation
Aug 31, 2026
Merged

fix(jcl): recognize PARM= on continuation lines, bound Mode A args search#2518
squid-protocol merged 3 commits into
mainfrom
fix/jcl-2482-args-continuation

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Summary

Fixes #2482 and #2483 — both are the same underlying JCL args (PARM=) gap/over-count, addressed together since they share one mechanism.

  • Extraction hardening: jcl args continuation lines #2482 (recall): the args regex only matched PARM= when it sat on the EXEC statement's own physical line. Real corpus JCL routinely continues PARM= onto a // continuation line — sometimes two hops away (CICSTS56.jcl's CICS step: EXEC line ends in a comma, then a COND=(...) continuation line also ends in a comma, and only the third line carries PARM=). Extended the regex with a bounded (≤8 hops), single-line-per-repetition continuation-crossing group — same-line PARM= (the common case) is untouched.
  • Extraction hardening: jcl args multi-line over-counting #2483 (over-count): Mode A's generic args-count derivation searched the whole greedy block for jcl, unlike cobol/fortran/dockerfile which already got a bounded search window via Mode A (_slice_by_labels) generic args-count search is unbounded, can attribute unrelated text to a function's parameter count #1973. This let a multi-line PARM='...' string — or worse, a later, unrelated step's own PARM= — get misattributed to the wrong step. Confirmed real: ZOSCSEC.jcl's BPXIT step read args=7 off an unbounded sweep of its own multi-line PARM='SH chmod ...' string (documented in docs/language_status/jcl.md). Added jcl's own comma continuation marker to _MODE_A_ARGS_CONTINUATION_MARKER so it shares the same window-bounding fix as the other three languages.

The nested-paren nuance in some real PARM=(...) lists (e.g. PARM=(...,'AMODE(31)',...)) is a separate, pre-existing limitation (regex can't balance nested parens in one bounded pass) — untouched by this PR, same as before.

Verification

  • tests/extraction/languages/test_jcl_strict.py / test_jcl.py: added regression tests for both issues (continuation-line recall at 1 and 2 hops, multi-line PARM=(...) values, same-line regression, no-bleed-into-later-step, and a dedicated ReDoS sweep for the new bounded hop group), plus a pipeline-level test exercising StructuralExtractor directly to prove the window-bound actually prevents cross-step bleed and over-counting (not just the regex in isolation). Removed a now-stale xfail marker whose "cannot parse JCL line continuations" reason no longer applies.
  • ruff_audit.py --ci / mypy_audit.py --ci: clean, no new findings.
  • Full non-golden-crucible test suite: passing.
  • Isolated corpus diff (since the local language-crucible sibling checkout was on a stale v1.0-era snapshot rather than the actually-pinned v1.2.0 — cloned an isolated v1.2.0 copy to avoid disturbing another in-progress session's shared corpus checkout): ran galaxyscope directly with this branch's own .crucible_venvs against unmodified main's, on the identical v1.2.0 corpus, and diffed the two fresh outputs against each other directly (bypassing the committed fixture). Result, in both full-precision and zero-dependency mode: 269 differences, all confined to jcl/ paths, zero ripple into any other language — e.g. ZOSCSEC.jcl's BPXIT step's Input Parameters drops from the documented bogus 7 to the correct 1.

Note on golden master fixtures (intentionally NOT re-blessed here)

A control run (main's own fresh scan of the correctly-pinned v1.2.0 corpus vs. the committed tests/golden_master_audit.json) already shows 1000+ differences on unmodified main — pre-existing drift unrelated to this PR (spans unrelated languages/fields, e.g. a Doc Umbrella field flattening to 0.0 across many languages, and cumulative_risk ranking swaps). I can't rule out this is a local-venv dependency-version artifact (my ad hoc pip install -e . + extra-deps venv vs. CI's exact pinned versions) rather than a real main-branch regression, and either way it's out of scope for this PR. Re-running update_golden_master.py locally right now would bake that unrelated drift into this PR's diff alongside the legitimate jcl-only change above, so I've left the fixtures untouched and am relying on CI's crucible-audit check (which runs in the exact pinned environment) as the authoritative signal for whether/what to re-bless.

Test plan

  • test_jcl_strict.py + test_jcl.py pass, including new regression tests
  • ruff_audit.py --ci / mypy_audit.py --ci clean
  • Full test suite passes
  • Isolated diff against unmodified main on the correctly-pinned corpus: change confined to jcl/, both full-precision and zero-dependency modes
  • Watch crucible-audit / tri-comparison-audit CI checks on this PR and address any real (non-pre-existing) drift they flag

🤖 Generated with Claude Code

…arch (#2482, #2483)

The `args` regex only ever saw PARM= when it sat on the EXEC statement's own
physical line, missing ~16-18 real corpus occurrences where it lands on a
`//` continuation line instead (sometimes two hops away, e.g.
CICSTS56.jcl's CICS step). Extended the regex with a bounded (<=8 hops),
ReDoS-safe continuation-crossing group.

Separately, Mode A's generic args-count derivation searched the whole
greedy block for jcl (unlike cobol/fortran/dockerfile, which already got a
bounded window via #1973), letting a multi-line PARM='...' string -- or
worse, a later unrelated step's own PARM= -- get misattributed to the
wrong step (confirmed real: ZOSCSEC.jcl's BPXIT step read args=7 off its
own multi-line PARM= string). Added jcl's own comma continuation marker to
_MODE_A_ARGS_CONTINUATION_MARKER so it shares the same window-bounding
fix.

Verified both fixes are correctly scoped via an isolated diff (this
branch's fresh galaxyscope scan vs. unmodified main's, same corpus
snapshot pinned to language-crucible v1.2.0, full-precision and
zero-dependency modes): 269 differences, all confined to jcl/ paths, zero
ripple into any other language. Golden master fixtures intentionally left
unblessed -- a control run confirmed unmodified main already diverges from
the committed fixtures by 1000+ unrelated entries against the correctly
pinned v1.2.0 corpus (pre-existing drift, not introduced by this change),
so re-blessing locally right now would bake in unrelated noise alongside
this fix's legitimate diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

squid-protocol and others added 2 commits August 30, 2026 20:07
…line fix

Regenerated both tests/golden_master_audit.json and
tests/golden_master_zero_dep_audit.json via update_golden_master.py
(Python 3.11 + CI's exact dependency versions, language-crucible pinned to
v1.2.0) to reflect the args-recall/over-count fix from the previous
commit. 269 differences in each fixture, 100% confined to jcl/ paths --
confirmed matches CI's own crucible-audit diff on PR #2518 exactly.

Note for posterity: an earlier local regeneration attempt spuriously
showed 1282 differences spanning many unrelated languages (a "Doc
Umbrella" field flattening to 0.0 broadly). Root cause: the isolated
language-crucible clone used for that attempt lived under a path
containing a literal "tmp" directory component, and
guidestar_lens.py's _calculate_documentation_coverage skips any directory
whose FULL path (any ancestor component, not just the immediate dir name)
matches IGNORED_DIRECTORIES -- which includes "tmp". Not a real
environment/dependency mismatch, not jcl-related, and not touched by this
commit; re-cloning the corpus to a tmp-free path reproduced CI's exact
269-diff result on the first try.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tinuation

# Conflicts:
#	tests/golden_master_audit.json
#	tests/golden_master_zero_dep_audit.json
@squid-protocol
squid-protocol merged commit 5c06080 into main Aug 31, 2026
30 checks passed
@squid-protocol
squid-protocol deleted the fix/jcl-2482-args-continuation branch August 31, 2026 00:22
squid-protocol added a commit that referenced this pull request Aug 31, 2026
…#2620)

* fix(detector): exclude synthetic slicer bucket names from orphan/duplicate census (#2547)

Languages sliced by Mode D (_slice_by_keywords: shell/ruby/lua/elixir/livecode/
matlab) or Mode E (_slice_by_terminator: sql/sqlite/plpgsql/...) have no real
same-file call graph. Mode D bundles any top-level loose code before the first
real scope into a synthetic "__global_context__" satellite; Mode E names every
top-level statement generically from its keyword ("SELECT_Statement",
"CREATE_Statement", ...) rather than a real captured identifier. Neither is a
genuine callable function, so the intra-file orphan census's "does this name
appear anywhere else in the file" check was structurally guaranteed to flag
them -- confirmed against the #1096 control corpus: shell a.sh/b.sh reported 4
orphans for 3 real functions (the extra was __global_context__), and sqlite
main.sql reported orphaned_logic=7 for a file with zero real callable names at
all (every satellite was a synthetic Mode-E bucket).

The existing exclusion set (Unknown_Sat/Anonymous_Block/Main/Declarative_Block)
already recognized this class of problem but was incomplete: it missed
__global_context__ entirely, didn't strip the _[Truncated]/_[Unterminated]
suffixes the slicer appends to those same names, and never covered Mode E's
<KEYWORD>_Statement shape. It also only guarded the orphan branch, not the
duplicate-detection branch reachable by the same synthetic names.

Replace it with _is_synthetic_satellite_name(), which recognizes the full
family (suffix-stripped exact names + the Statement-name pattern) and gates
both branches. This directly fixes the orphan->api conversion (Contextual
Baseline Fix, galaxyscope.py ~2145) inflating a popular file's api count with
non-function shapes.

Re-verified against the real corpus post-fix: a.sh/b.sh api 4->3, main.sh
orphans 2->1, main.sql orphans 7->0, a/b/c.sql's bogus api 2/4/3->0/0/0 (SQL
statements were never real API surface -- Mode E never captures a real
identifier for any SQL statement type, so the correct count is zero, not one
per CREATE INDEX as initially assumed).

Golden master fixtures regenerated (tests/tools/update_golden_master.py, both
full-precision and zero-dependency modes) -- the ~2200 diffs are the expected,
intentional consequence of this fix across every shell/sql/lua/livecode/ruby
file in the language-crucible corpus with either shape. crucible_check.py and
the full test suite (7168 tests) pass clean after regeneration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: harden crucible_check.py against env drift, add scope_check.py, fix ci-push-checklist

Motivated by PR #2518 (jcl args continuation-line fix): verifying that fix was correctly
scoped took far longer than it should have, chasing environment issues that had nothing to
do with the actual code change. This bundles what that investigation found into reusable
tooling so the next fix doesn't re-pay the same cost.

crucible_check.py:
- Prefers a `uv`-managed Python matching CI's own pin (parsed from
  .github/workflows/golden-crucible.yml) when building a venv, instead of whatever
  interpreter happens to be running the script -- a version mismatch can resolve different
  releases of unpinned optional deps (networkx/pandas/etc.), producing numeric drift
  unrelated to the change under test.
- Warns if the local language-crucible corpus isn't on the pinned tag, and if its own
  absolute path contains an IGNORED_DIRECTORIES name (e.g. "tmp") as any path component --
  both produce thousands of phantom diff lines with no error message otherwise.
- Verifies (and retries) that a venv's editable install actually resolves to the intended
  repo_root before returning it, via a new _venv_env() that strips PYTHONPATH from every
  subprocess invoking a specific venv's python. An inherited PYTHONPATH pointing at a real
  gitgalaxy/ package (trivially easy to have set for unrelated reasons) silently shadows the
  venv's own editable-install finder with no error at all -- confirmed to fully explain a
  chain of false leads (a wheel-cache-collision theory, a setuptools_scm worktree-root
  theory, a python -c cwd-shadowing theory) while building this fix, before the actual cause
  turned up. See the module's own docstring for the full writeup.
- ensure_venv() now accepts an explicit repo_root, so a second venv against a different
  worktree can reuse the exact same build logic.

scope_check.py (new): isolates a change's real-corpus impact to the language(s) it should
touch, independent of whether the committed golden master fixture is even current. Scans the
working tree and a comparison ref (default origin/main) fresh in separate venvs, diffs them
directly, and buckets every difference by language -- `--expect <lang>` fails loudly if
anything outside that set changed. This is the exact by-hand methodology used to verify
PR #2518's fix was jcl-only, now a single command instead of ~15 ad hoc one-liners.

ci-push-checklist skill: added time estimates per section, a step -1 pointing at the
self-scan DB/architecture brief for orientation, and corrected section 3's corpus-cloning
guidance -- the prior "never clone a fresh copy, metadata/footprint shifts topology" claim
was wrong about the mechanism (verified by direct repro); the two real causes are corpus pin
drift and unsafe path naming, both now caught automatically. Also documents scope_check.py
and points section 6's post-conflict-resolution step at it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Joe Esquibel <squid-protocol@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
squid-protocol added a commit that referenced this pull request Aug 31, 2026
… + ci-push-checklist (#2621)

Two friction points from fixing #2547 that cost real time/tokens, distinct from #2620's
crucible_check.py env-drift hardening (a different issue, #2518):

1. No documented technique for empirically inspecting what detector.py actually extracted
   for a given file -- root-causing the #2547 bug required reading _slice_by_keywords/
   _slice_by_terminator cold before realizing `galaxyscope --debug` + grepping the
   WORKER-TRACE log line is far cheaper. Added as its own CLAUDE.md section, cross-linked
   from the ci-push-checklist skill's "Local & Unit Validation" step.

2. CLAUDE.md's Differential Scan section told a reader to regenerate golden masters via
   `python tests/tools/update_golden_master.py` directly -- technically correct (it's the
   sanctioned mechanism) but misleading: it silently updates only whichever ONE fixture
   matches whatever's importable in the current shell, with none of crucible_check.py's
   venv/PATH automation. The better path (`crucible_check.py --update --yes`, which handles
   both modes through properly-scoped venvs) was already documented 10 lines below in the
   same section, just never cross-referenced -- reading the first instruction and stopping
   there (as I did) cost a full manual venv-wrangling cycle. Reordered so the automated
   entry point is what a reader hits first, with update_golden_master.py explained as the
   underlying single-mode script it wraps.

Also noted (ci-push-checklist): Claude Code's Auto Mode classifier blocks blessing a golden
master by default, even via --yes on a no-op clean tree -- worth expecting up front rather
than hitting it as a surprise mid-task.

Docs only, no code/fixture changes.

Co-authored-by: Joe Esquibel <squid-protocol@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.

Extraction hardening: jcl args continuation lines

1 participant