feat(extraction): add COBOL language support (.cbl/.cob/.cpy) (#590, #648) (#1161)

Programs, sections/paragraphs (reconstructed extents over the grammar's
flat header stream), PERFORM/THRU/GO TO/CALL call edges, COPY copybook
imports incl. standalone .cpy fragments, DATA DIVISION records/fields/
88-levels with write-site impact references, and CICS flows: EXEC
LINK/XCTL program targets (literal + same-file VALUE deref), EXEC SQL
INCLUDE, and pseudo-conversational RETURN/START TRANSID hops resolved
to the owning program via a CICS framework resolver. Fixed and free
source format (free format via a scanner wide-mode sentinel).

Grammar: vendored wasm built from a patched yutaro-sakamoto/
tree-sitter-cobol (EXEC blocks as an external-scanner token, copybook
fragment entry point, single-quote continuation, COPY REPLACING
pseudo-text, NOT=, CALL GIVING, ENTRY, FREE, bitwise ops, abbreviated
relations, COBOL-2002 usages, and more). Patch + provenance + upstream
PR draft in docs/grammars/. Parse health: AWS CardDemo 43/44 native
(upstream: 9/31), 44/44 through preParse; copybooks 28/29; CobolCraft
free-format 17/17 (upstream: 0); NIST COBOL85 unchanged at 373/382.

Copybook members resolve to files like C includes (basename index,
name-matcher short-circuit so compiler-supplied members stay honestly
unresolved): CardDemo imports 5 -> 285. Impact proof: ACCT-CURR-BAL
(CVACT01Y copybook) surfaces its 4 writer programs cross-file.

Also: run-all.sh now neutralizes the ambient prompt-hook in both A/B
arms (CODEGRAPH_NO_PROMPT_HOOK=1); COBOL corpus entries for agent-eval.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-03 09:17:53 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 7d624ecfac
commit 41620c60fa
16 changed files with 1612 additions and 6 deletions
+91
View File
@@ -0,0 +1,91 @@
/**
* CICS Framework Resolver (COBOL)
*
* Resolves the pseudo-conversational transaction hop: a program ends with
* `EXEC CICS RETURN TRANSID('CB00')` (or START), and CICS re-invokes the
* program that OWNS transaction CB00 on the next attention key. The
* transaction→program mapping lives in the CICS CSD, which is never in the
* repo — but by near-universal convention each program declares its own
* transaction id as a working-storage constant:
*
* 05 WS-TRANID PIC X(04) VALUE 'CB00'.
*
* The COBOL extractor emits `cics-transid:CB00` call references for literal
* (or same-file-dereferenced) TRANSID options; this resolver maps the id to
* the program module whose TRAN*-named data item declares that VALUE. No
* match (an id owned by a program outside the repo) stays unresolved.
*/
import { FrameworkResolver, UnresolvedRef, ResolvedRef, ResolutionContext } from '../types';
import { Node } from '../../types';
const TRANSID_REF_PREFIX = 'cics-transid:';
/** Data items that name a transaction id by convention. */
const TRANID_NAME_RE = /TRAN/i;
const VALUE_LITERAL_RE = /\bVALUE\s+['"]([A-Za-z0-9$#@]{1,4})['"]/i;
/**
* transaction id → owning program module, built once per resolution context.
* A WeakMap so a per-ref scan of every data node can't go quadratic on
* copybook-heavy repos.
*/
const transidIndexes = new WeakMap<ResolutionContext, Map<string, string>>();
function buildIndex(context: ResolutionContext): Map<string, string> {
const index = new Map<string, string>();
const dataNodes: Node[] = [
...context.getNodesByKind('variable'),
...context.getNodesByKind('field'),
...context.getNodesByKind('constant'),
];
for (const node of dataNodes) {
if (node.language !== 'cobol') continue;
if (!TRANID_NAME_RE.test(node.name)) continue;
const value = node.signature ? VALUE_LITERAL_RE.exec(node.signature) : null;
if (!value?.[1]) continue;
const tx = value[1].toUpperCase();
if (index.has(tx)) continue; // first declaration wins; collisions are rare and ambiguous
const moduleNode = context
.getNodesInFile(node.filePath)
.find((n) => n.kind === 'module' && n.language === 'cobol');
if (moduleNode) index.set(tx, moduleNode.id);
}
return index;
}
export const cicsResolver: FrameworkResolver = {
name: 'cics',
languages: ['cobol'],
detect(context: ResolutionContext): boolean {
// Any indexed COBOL program qualifies — the resolver only ever acts on
// cics-transid: references, which only the COBOL extractor emits.
return context.getNodesByKind('module').some((n) => n.language === 'cobol');
},
// cics-transid:XXXX matches no symbol name — opt it past the
// name-exists pre-filter so it reaches resolve().
claimsReference(name: string): boolean {
return name.startsWith(TRANSID_REF_PREFIX);
},
resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
if (!ref.referenceName.startsWith(TRANSID_REF_PREFIX)) return null;
const tx = ref.referenceName.slice(TRANSID_REF_PREFIX.length).toUpperCase();
let index = transidIndexes.get(context);
if (!index) {
index = buildIndex(context);
transidIndexes.set(context, index);
}
const targetNodeId = index.get(tx);
if (!targetNodeId) return null;
return {
original: ref,
targetNodeId,
confidence: 0.85,
resolvedBy: 'framework',
};
},
};
+3
View File
@@ -27,6 +27,7 @@ import { swiftObjcBridgeResolver } from './swift-objc';
import { reactNativeBridgeResolver } from './react-native';
import { expoModulesResolver } from './expo-modules';
import { fabricViewResolver } from './fabric';
import { cicsResolver } from './cics';
/**
* All registered framework resolvers
@@ -70,6 +71,8 @@ const FRAMEWORK_RESOLVERS: FrameworkResolver[] = [
expoModulesResolver,
// React Native Fabric / Codegen view components — TS spec → component nodes
fabricViewResolver,
// CICS pseudo-conversational TRANSID hops (COBOL)
cicsResolver,
];
/**