45 KiB
AgenticCode Roadmap — Open Tasks
This is a living task list of the remaining work. Completed features have
been moved to x-docs/features.md (with full implementation notes); this file
tracks only items that are still open.
The Web UI initiative (items 48–51) is largely delivered — backend prereqs 48–50 and frontend milestones M0–M5 are done (see
x-docs/features.md); only M6 (scale & polish) remains, and it is postponed as of 2026-07-15. Bug #58 (stale nodes surviverefresh), #59 (shared field tokenizer) and #43 (hash-based auto-invalidation + deleted-file sweep) were completed 2026-07-15; #61 (comments parsed as CALLNAT targets), #62 (data literals as false MODULE call targets), #63 (CALLNAT inside a string literal) and #64 (ingest summary contradicted the graph; dispatch guards lost theirVALUEalternatives) 2026-07-16 (seex-docs/features.md). #24 (the stale-file sweep scanned the wholeAstNodelabel once per file — a fullupmsrefresh went from ~90 min, incomplete, to 179 s) and #25 (batch persist, already implemented) were completed 2026-07-16. A manualWGEAGB0Sendpoint audit against the Natural source (2026-07-16) closed #65 (?depth=now counts module hops — it had silently reported "no DB access" for a module two calls from its tables) and #66 (the API dropped the copycode line/file provenance the graph carries, so a.cpyline was served as a host line). #69 (edges on the same line number from different files collapsed — data loss, found by #66's own fixture) and #70 (placeholder field resolution dropped the copycode provenance, so item 66 only worked for bare field references) were completed 2026-07-16, and #68 (field-flowbounded on rawCALLShops — it reported "nothing consumes this field" for a consumer called from inside a subroutine, and introduced the derivedCALLS_MODULEedge) and #67 (call-treereported a direct dependency as 3 hops deep, and now flagstruncatedwhen its traversal budget cuts) 2026-07-17. The whole raw-hopdepthfamily (65/67/68) and the copycode-provenance family (66/69/70) are closed. (Item 67's follow-up #71 was retracted 2026-07-17: it rested on a measurement error of mine — the correct count is 0, and the parser cannot produce the edge it assumed. See "Not a bug".) AVMULTMN4audit (2026-07-17) found #72 (a nestedDECIDEdropped the outer guard, so the dispatch table stated an incomplete condition as complete — 178 upms modules); #72 is fixed 2026-07-17 (seex-docs/features.md) and left #73 (aNONEbranch is a negation no guard chain can express) and #74 (a resolved and a placeholder edge for one statement coexist — exposed, not caused, by #72). Item 26 (MCP session reliability) remains postponed as of 2026-07-13. All remaining open items are postponed; revisit when prioritised.
Web UI — code understanding & navigation
A React/TypeScript web UI (ac-ui/) for navigating the AgenticCode graph to
migrate legacy Natural to Java. Full vision/architecture: x-docs/ui-proposal.md.
Backend prereqs 48–50 and frontend milestones M0–M5 (+ the M6 explorer regex
filter) are DONE — see x-docs/features.md. Only M6 scale/polish remains:
- 51 (M6). Scale & polish (POSTPONED 2026-07-15) — virtualisation/large-graph performance,
multi-project, auth, theming, export (SVG/PNG/report). (The rest of item 51 is
complete; see
x-docs/features.mdfor M0–M5 detail.) Key open risks carried from the earlier milestones: auto-ingest latency on large projects (deep-ingest concurrency cap defaults to 2); theSTALE_SOURCEwarm side-effect (a warming query re-ingests + re-hashes a module and thereby clears its staleness — treat ingest status as "true at query time"); node-idinstability after re-ingest (the UI keys onname + sourceFile); auth/multi-user and the deploy model still open.
Lazy / deferred ingest (three-tier model)
Reworks ingest from eager whole-project parsing into a lazy, on-demand model.
Three tiers: Tier 1 = cheap eager reference index (per file: nodes,
identifiers, coarse call/DB references — no deep bodies); Tier 2 = lazy deep
ingest (control flow, statement-level dataflow, precise reads/writes) triggered
on demand; Tier 3 = source served from the filesystem, no longer stored on
nodes. Reverse queries (callers, search_identifier, flow_backward) stay
answerable because Tier 1 pre-indexes coarse references globally.
Items 36–43 are done (Tier-1 reference index + tri-state status, Tier-2 lazy
deep-ingest, depth/node caps, unresolved-reference nodes, Tier-3 source-from-disk
with stale check, the refresh surface, and hash-based auto-invalidation) — see
x-docs/features.md. No open items remain in this track.
Ingest performance
(Items 24 — the stale-file sweep's missing (project, sourceFile) index, the actual persist
bottleneck — and 25 — batch persist, found already implemented — completed 2026-07-16 and moved to
x-docs/features.md. A full upms call-graph refresh (6311 files) now takes 179 s end-to-end
(~63 s parse, 104.5 s persist across 32 batches, ~12 s finalize), against ~2.5–2.8 min per batch
before. The parked "parallel parse phase" idea was implemented 2026-07-18 (item 24) — see
x-docs/features.md. No open items remain in this track.)
Known bugs
-
75.
CONTAINSis not acyclic — 22 self-loops and 162 two-cycles inupms(found 2026-07-17 while root-causing item 74; cause NOT established — do not treat the notes below as settled). The containment hierarchy that dozens of queries traverse withCONTAINS*contains cycles:(IF @ JX0031N0.nat:781-966) -[:CONTAINS]-> (FOR @ YFRAMBC0.cpy:65-15) (FOR @ YFRAMBC0.cpy:65-15) -[:CONTAINS]-> (IF @ JX0031N0.nat:781-966)Neo4j's variable-length patterns use trail semantics (no relationship repeats in a path), so queries terminate rather than hang — but the blow-up is real: three probes using an unbounded
CONTAINS*over this region were killed at a 2-minute timeout during the item-74 investigation. Candidate, unconfirmed: copycodeCONTROL_FLOWnodes are shared by every including module (one node per.cpyline), and a.cpythat opens a block it does not close (YFRAMBC0.cpyopensFORat line 65; theEND-FORlives in the includer) gets containment edges from every includer's nesting context accumulated onto that one shared node. Related: 625 nodes haveendLine < startLine(531DB_ACCESS, 94CONTROL_FLOW) — theFORabove is65 -> 15. But this explains only 26 of 162 cycles and 4 of 22 self-loops, so it is not the main cause. Left open on purpose rather than guessed at. The cycles are not confined to the statement tree: theDATA_STRUCTUREnode named","inBSUPLFN0.nat(itself a parser artifact worth its own look) carries self-loops, so theINCLUDES -> fieldwalk the bare-field resolvers do runs through cyclic ground too. That is why item 77'sINCLUDE_FIELD_DEPTHbound is a correctness requirement, not a tuning knob — an unboundedCONTAINS*there is what killed the probes above. (Item 77's redirect does not traverse this region:srcfor a placeholder edge is only everFUNCTION(157,616 edges) orMODULE(35,572) — neverCONTROL_FLOW— and all 16,231 such functions are directCONTAINSchildren of a module, so its*0..1bound avoids the cycles entirely.)2026-07-17 — concrete impact established and fixed for the dynamic-
CALLNATfamily (pending corpus re-verify). The blow-up is not merely theoretical: a whole-rootdeeprefresh ofupmswedged finalize step 17 (resolve-dynamic-callnat-intra-indirect) for ~2 h without completing, which blocks every later step — including item 77's bare-field resolution, so item 77 could not be corpus-verified. Measured cause: that step joins three unbounded(caller:MODULE)-[:CONTAINS*0..]->anchors, and the resulting per-caller path enumeration over the cyclic copycode region is cubic — a read-only probe of the exact query for a single caller (DAGNTFN0) did not finish in 60 s. Fix: the six dynamic-CALLNATresolvers (RESOLVE_DYNAMIC_CALLNAT_INTRA/…_INDIRECT/…_CROSS- their
…_SCOPEDvariants inCypherQueries) no longer descendCONTAINSto find a module's own statements. AMODULEis 1:1 with itssourceFile(verified: 3587 files, max one module each), and a module'sCALLNATsites andWRITESstatements all carry that samesourceFile, so the anchors becomesourceFile-equality hash-joins that cannot cycle. The dispatch variable in the cross-module resolver can live in an included PDA, so it is scoped to the caller's own file or a data structure the callerINCLUDES(matching by name alone would pull in 20958 unrelated same-named vars; the scope filter keeps the 307 in-scope ones). Proven equivalent onupms:resolve-dynamic-callnat-intrayields the identical 31 resolved(caller, target, lineNo)triples project-wide, and the rewritten indirect step completes project-wide in ~6 s. Existing dynamic-dispatch ITs (intra / indirect / cross / scoped / unresolved- survival) stay green. Still to do: deep-recreateupmsand confirm finalize reaches 36/36; then finish item-77 corpus verification. This does not remove the underlyingCONTAINScycles — otherCONTAINS*traversals remain exposed if a future step joins several of them; the cycles themselves (parser line-range/shared-copycode artifacts) are still open above.
2026-07-18 — same blow-up confirmed on the READ path (frontend-facing, NOT yet fixed). Only the finalize write-queries were rewritten above; the runtime read-queries the UI (
ac-ui) renders still use unbounded(m:MODULE)-[:CONTAINS*0..]->(src). Measured live against server v71 on a heavy module (ACCNPE01):callees>2 min / hangs,digest>10 s timeout,context>10 s timeout,callers~3.6 s; light modules (BMTABBP0) anddb-accesses/call-tree/graph/functionsstay <1.2 s. So the UI's Callees / module-overview / context panels spin on large modules. Same cure as the dynamic-CALLNATfix (src.sourceFile = m.sourceFilehash-join, INCLUDES-scoped for variables). Affected read constants:CALLEES,CALLERS,DIGEST/CONTEXTquery,FUNCTION_CALLERS,DB_ACCESSES, variableREADS/WRITES,*_FOR_MODULES. 2026-07-19 — fixed. Rather than thesourceFilehash-join, the read queries use a tighter, provably equivalent bound: every {@code CALLS}/{@code READS}/{@code WRITES}/{@code DB_ACCESS}-parent edge source is a {@code MODULE} (depth 0) or a {@code FUNCTION} that is a direct {@code CONTAINS} child of the module (depth 1) — verified corpus-wide (0 sources deeper, 0 non-direct-child edge-source functions, and {@code DB_ACCESS} parents are only {@code FUNCTION}/{@code MODULE}, never {@code CONTROL_FLOW}). So(m)-[:CONTAINS*0..]->(src)becomes(m)-[:CONTAINS*0..1]->(src), which returns the identical set but cannot walk the cyclic copycode region. 20 read-side traversals updated (callees,MODULE_HOP_OUT(+ wiring),DISPATCH_TABLE,EGO_NEIGHBORS_*,VARIABLE_ACCESSES,DB_ACCESSES(+FOR_MODULES),SQL_STATEMENTS(+FOR_MODULES),FUNCTION_CALLERS,SEARCH_BY_VALUE(+_CONTAINS),fieldFlow,BUILD_CALLS_MODULE,FLOW_FRONTIER_SOURCE_FILES). Finalize/resolve queries left as-is (they completed). Guarded byReadPathBoundedTraversalIT(EXPLAIN plan asserts no unbounded CONTAINS expand incallees); full IT suite green (193/0/0). Norecreateneeded — a query-only change against the existing graph. Verified live (v76):ACCNPE01 callees>2 min → 0.16 s,digest>10 s → 3.3 s,context**>10 s → 3.1 s;WGEAGB0S calleesunchanged (7). The underlyingCONTAINS` cycles (parser artefacts) still exist, but both the finalize and the read consumers are now bounded — item 75 no longer has a practical impact. - their
-
73. A
NONE/ANYbranch is reported under its enclosing guard alone — the condition is a negation no guard chain can express (found 2026-07-17 while fixing item 72; item 72 does not fix this).NaturalParser'sVALUE_RESETclears aDECIDE's active value onNONE/ANY, so an assignment inside such a branch is attributed to the enclosing guard chain only. UnderVALUE 'TABL'→DECIDE ON #FIELD-NAME→NONE→MOVE ..., item 72 now reportsguards = [#SHORT-VIEW='TABL'], which reads as "happens for all of TABL". The truth is "#SHORT-VIEW = 'TABL'AND NOT (#FIELD-NAME= any of the branch'sVALUEs)". This is the same complaint item 72 makes — a condition served as complete when it is not — so item 72'sguardsis not a total answer, only a strictly better one. A guard chain is a conjunction of equalities by construction; expressing this needs a negated link (e.g. aDispatchGuardwithnegated=truecarrying the sibling branches' values), which is a model change, not a parser tweak. (Unmeasured: how manyNONEbranches actually contain assignments — many areIGNORE. Worth counting before investing.)
(Fixed bugs 55, 57, 58, the dossier field-ordering fix, and the raw-hop depth family — 65, 67
(call-tree), 68 (field-flow) — plus 69/70 (edge identity + copycode provenance) moved to
x-docs/features.md. #71 is a known imprecision left behind by item 67 rather than a wrong answer; #72 is a
real
wrong answer, found by the 2026-07-17 VMULTMN4 audit.)
-
Not a bug — retracted 2026-07-17 (was #71): "external subroutine calls do not count as a module hop" rested on a bad measurement of mine, not on the code. I counted 4,808 "genuine external subroutine calls" in
upmswith(ma)-[:CONTAINS*0..]->(src)-[:CALLS]->(f), applying the single-owner filter to the target but not to the source: for a copycode-shared source function (L4N-ENTERisCONTAINSed by 137 modules) that enumerates all 137 asma, each differing from the target's owner. The correct test — source and target single-owner — returns 0. And it must:resolvePlaceholderTargetsfiltersph.type IN ['MODULE', 'DATA_STRUCTURE'], so aFUNCTIONplaceholder is never resolved across modules; aPERFORMto another module's subroutine stays an unresolved placeholder (6 inupms) and never becomes a cross-moduleCALLSedge. A path therefore cannot enter a module at aFUNCTIONnode, which was #71's entire premise. (The real, tiny gap: those 6 unresolved placeholders. Natural external subroutines are a language feature this parser does not resolve — worth its own item if the corpus ever needs it.) -
98. View aliases declared in a
LOCAL USINGdata area are still reported as tables (found 2026-07-27 while re-verifying item 95). Item 95's alias pre-scan is per-module, over the copycode-expanded lines — but aLOCAL USINGdata area is a separate module, so a view declared there is invisible to it. Live example:YGEAGBNH.nat:2617doesFIND (1) VDB2-VERSIS_GENAGREE, and the alias is declared insrc/manual/local_data_area/new/YGEAGVL1.lda, not inYGEAGBNH— sodb-accessesstill reportsVDB2-VERSIS_GENAGREEinstead ofVERSVW_GENAGREE. It is the last such row in theWGEAGB0Sclosure (58 → 1), but 348 view aliases are declared corpus-wide, so the class is probably wider than this one instance. Not a parser fix:parseDataAreaalready emitsDATA_STRUCTURE --USES_TYPE--> DB_TABLEfor a.lda/.pdaview, so the graph holds the link. Likely shape: a post-ingest enricher redirecting aDB_TABLEwhose name matches a view-aliasDATA_STRUCTUREonto the table that structureUSES_TYPE, then reaping the orphaned alias node. Sizing it properly wants aGET /db-tableslisting endpoint, which does not exist yet. -
97.
call-treeleaked the dynamic-call placeholder a manual override only hides (2026-07-27, third WGEAGB0S deep API audit).call-treeforWGEAGB0Slisted#GETSHORT-MODUL— a variable (YGEAGGNH.nat:443,CALLNAT #GETSHORT-MODUL) — as aMODULEin the closure, whilecalleesfor the same module correctly reported only the resolved targetYGEAGGN0. Cause: a manual override does not delete the marker edge to the variable-named placeholder, it setsmanualHidden = trueand relies on the read queries to suppress it (DELETE_DYNAMIC_CALLNAT_PLACEHOLDER_EDGES).callees/callersfilter it; the BFS behindcall-treedid not —MODULE_HOP_OUT/MODULE_HOP_OUT_WIRINGdid not even bind the relationship. Everything driven by that BFS inherited the pollution (graph,db-accesses?depth=N,sql-statements?depth=N). Done: both hop queries bindrand applycoalesce(r.manualHidden, false) = false, matchingcallees/callers. Characterization ITDynamicCallOverrideIT#callTreeHonoursTheOverrideLikeCallees(placeholder present → override → absent → reset → present again); verified red against the pre-fix query. -
96. Natural
UPDATE(ref.)/DELETE(ref.)were dropped, hiding every access layer's write path (2026-07-27, third WGEAGB0S deep API audit). 34 statement sites across 13 of the 65 modules in theWGEAGB0Sclosure — everyY****MN0CRUD module — produced noWRITESedge, sodb-accessesshowed them as read-only plus a singleSTORE. Cause:DB_WRITE's(?!\()guard (added by item 90 to stop a phantom(OLD.)table) suppressed the phantom but never recovered the real table, andDELETEwas only handled in its SQLDELETE FROMform. Done: newDB_WRITE_BY_REFplus a pre-scan mapping eachFIND/READstatement label to its (alias-resolved) table; an unresolvable reference still records nothing, so item 90's no-phantom guarantee holds — its two tests stay green unchanged and now serve as the negative cases. Shared withNaturalCoarseScannerso tier-1 and deep agree. Tests:NaturalParserTest#updateAndDeleteByReferenceResolveToTheEnclosingLoopTable,#byReferenceWriteWithoutAResolvableLoopNamesNoTable,NaturalCoarseScannerTest, ITNaturalViewAliasDbAccessIT. A label may also introduce a SQLSELECTloop rather than aFIND(YELEMMN0,YMULTMN0hold their record that way) — those resolve through theFROMclause;#byReferenceWriteResolvesThroughALabelledSelectLoop. Verified on liveupms: all 34 by-reference sites in theWGEAGB0Sclosure now recorded, 0 missing. -
95. Natural view aliases were reported as DB tables (2026-07-27, third WGEAGB0S deep API audit).
db-accessesnamed the Natural view variable of a DML statement, not the DDM it is declared over: 58 rows across 13 of the 65 modules in theWGEAGB0Sclosure, 32 alias names standing in for 20 real tables. Worst effects — the generator's boilerplate aliasNEXT-VIEWbecame oneDB_TABLEnode shared by 11 modules meaning 11 different tables (and reporting no columns), and 11VDB2-*-VLOGaliases hid every write toVERSVW_LOGFILE, so "who writes the audit log?" answered nothing. Cause:VIEW OFwas only recognised inparseDataArea(.pdafiles);parseModule— which parses every.nat— never built an alias map, and the DML branches passed the operand verbatim todbTable(...). Done:VIEW_DECLpre-scan over the copycode-expanded lines feedsresolveViewAliasinto theDB_WRITE/DB_READbranches;dbTable()now upper-cases (Natural is case-insensitive andDB_TABLEmerges on the name). Shared withNaturalCoarseScannerso a shallow and a FULL module cannot report different names for the same statement. Tests:NaturalParserTest#viewAliasResolvesToTheUnderlyingTable,NaturalCoarseScannerTest, ITNaturalViewAliasDbAccessIT(incl. the same alias in two modules resolving to two tables). Verified on liveupms: alias rows in theWGEAGB0Sclosure 58 → 1, the phantomNEXT-VIEWnode gone,VERSVW_LOGFILEreachable for the first time. The remaining row is the cross-file case, item 98. -
94.
call-treeno longer enumerates paths;followWiringusable again (2026-07-20, JX0034N0 ↔ MultiTableImportJob functional comparison).call-tree?followWiring=truetimed out onpuratdepth ≥ 2(>120s; depth 1 already took 5.3s), which made the Java wiring closure unobtainable. Measured cause — a single quantified path pattern overCALLS|INJECTS|REFERENCES, bounded bymaxDepth × (1 + internalBudget)(= 42 at depth 2), recovering each target's depth asmin(#MODULE nodes on path) - 1, i.e. by enumerating every path. With the CHA-materialized wiring edges (items 31/92) that is combinatorial:rawBound Java, followWiring, depth 2Natural JX0034N0, depth 53 1.6s, 71 targets — 4 1.7s, 71 targets 2.6s, truncated (42) 6 18.9s, 71 targets 3.2s, truncated (79) 8 / 12 >120s 2.2s / 2.4s, truncated (120/172) 21 >120s 3.9s, converged (182) 42 (production) >120s 3.2s, 182 So the budget is necessary for Natural (whose result converges only near 21) and useless for Java (converged at 3) — lowering it globally would silently truncate Natural, the exact failure its javadoc warns about. The blow-up comes from the wiring edges, which
JavaParser.addWiringEdgesand the CHA steps only ever emit class-to-class, so they can never reach aFUNCTION. Fix: split the query.MODULErows now come straight from themoduleDepthsBFS (its hop index is the module-hop depth — verified equal to the old query's module set: 71/71 for Java, 46/46 for Natural), and onlyFUNCTIONrows still traverse,CALLS-only and bounded within one module. Behaviour change: the budget can no longer hide a module whose call site sits behind a long internalPERFORMchain —DEPTHLEAFis now reported at depth 1, which also removes a standing contradiction withdb-accesses/sql-statements, whose module set always came from the same BFS.truncatedaccordingly now means "some module's internal subroutine chain may be cut off". Covered byCallTreeTruncationIT(both tests).Measured live after deploy —
call-tree?followWiring=trueonMultiTableImportJob: depth 2 1.9s (was >120s) with the same 71 modules, depth 6 3.0s, depth 10 2.3s converging at 852 modules. Onupms/JX0034N0at depth 5 the module set grew 46 → 53 with nothing lost; the seven that had been hidden areNDBERR,NDBNOERR,USIX009N,USIX052N,USIX053N,YELEMGN0,YLITEMN0. They are real:USIX052NisCALLNATed byISI173N0(line 474), itself a direct callee ofJX0034N0, so it sits at module depth 2; andYLITEMN0was already reported bydb-accesses?depth=5as avia, which is the contradiction this item removes. -
93. Transitive
db-accesseslost theDECLARESrows (2026-07-20, JX0034N0 ↔ MultiTableImportJob functional comparison).DB_ACCESSESresolves a table from three sources —READS/WRITES, an entity's ownMAPS_TO, and a repository'srepositoryEntity(item 32) — but its transitive counterpartDB_ACCESSES_FOR_MODULES(item 65) only ever had the first. The transitive view was therefore not a superset of the direct one: asking the same module withdepthsilently dropped its table. Minimal repro:db-accessesonMultiTableEntryEntityreturnsmulti_table_entry/DECLARES,db-accesses?depth=1on that same module returns[]. Consequence: a Java caller's transitivedb-accessescame back empty even though the entity it persists through maps to a real table, which made the Java side of a Natural↔Java DB comparison impossible to obtain from the API. Fix:DB_ACCESSES_FOR_MODULESnow carries the same three UNION branches, withvianaming the module that declares the table.SQL_STATEMENTShas no such branches, soSQL_STATEMENTS_FOR_MODULESneeded no change (verified). Covered byJavaRepositoryOwnTableIT.entityTableAlsoResolvesInTheTransitiveView/repositoryTableAlsoResolvesInTheTransitiveView(both red before the fix). -
92. Java inheritance/CHA wiring: three defect classes fixed (2026-07-19, MultiTableImportJob deep API audit — manual Java source pass, project
pur). Three systemic errors in the callees/wiring materialization, all found by comparingcalleesagainst source:- A — inherited
INJECTS/REFERENCESlost their origin file (635× in the MTIJ closure, 255 with alineNopast the caller file's end).LINK_REFERENCES_TO_SUBCLASSES/LINK_INJECTS_TO_SUBCLASSES(item 31) copied the base class'slineNoonto the subclass edge but never setoriginFile, so thecalleessites(coalesce(r.originFile, source.sourceFile)) fell back to the subclass file. Worked example:MultiTableImportJobreportsPurBatchJobListener REFERENCES lineNo=133, but that file has 93 lines — line 133 is inAbstractPurBatchJob.java. Same as the Natural copycode bug (items 66/91), for Java inheritance. Fix: materialized edges now carryoriginFile = coalesce(r.originFile, base.sourceFile)+inheritedFrom = base.name. - B — CHA fanned constructor calls out to subtypes (48× phantom
CONSTRUCTORcallees).LINK_CALLS_TO_IMPLEMENTATIONSapplied class-hierarchy analysis tocallKind='CONSTRUCTOR'CALLS, sonew ArrayList<>()produced a phantom→ InputConstraintHolder [CONSTRUCTOR](itextends ArrayList), andnew BaseException()fanned out to every exception subtype. A constructor is statically bound. Fix:AND coalesce(r.callKind,'') <> 'CONSTRUCTOR'. - C — a qualified same-name supertype resolved to self (3× self-
EXTENDS).DateUtils extends org.apache.commons.lang3.time.DateUtils(andNumberUtils/StringUtils) were resolved by simple name to the project's own same-named class → aDateUtils EXTENDS DateUtilsself-loop that also poisoned the inheritance materialization. Fix:JavaParser.supertypeNamekeeps the FQN when a qualified supertype's simple name equals the declaring class's own name; the materializers additionally guardsub <> base. The parser fix stops new self-edges, but a non-wipingrefreshleaves the old self-EXTENDSbehind (both endpoints are the surviving class node, so node reconciliation never sweeps it — the edge gap item 86 closed for Natural), so adelete-self-inheritance-edgesenrichment step reaps any self-EXTENDS/IMPLEMENTSedge project-wide before the inheritance graph is traversed. - Infra: a
delete-synthetic-inheritance-edgesenrichment step reaps allresolvedVia:'INHERITANCE'edges before the three materializers rebuild them, so a non-wipingrefreshpicks up the new properties/gates (otherwiseMERGE ... ON CREATEnever updates a pre-existing edge). Query-only fixes for A/B + reap; parser fix for C. ITJavaInheritanceWiringIT(3 tests). No response-shape change —originFileflows through the existingcalleessites.callSiteFile.
- A — inherited
-
91.
db-accesses/workfile-accesses/sql-statementscarry copycode provenance (2026-07-19, third WGEAGB0S deep API audit — manual source pass). A DB or work-file access whose statement lives in anINCLUDEd copycode was reported with a copycode-locallineNoand no file context, so the number read as a line of the host module. Concretely: the DB2 sequence readSELECT … FROM SYSIBM-SYSDUMMY1lives inUSIX043C.cpyat lines 31/39/45/51/57;db-accessesfor the 9 including modules (YAPRFMN0, YCUACMN0, YLITEMN0, YMODAMN0, YMTABMN0, YMULTMN0, YPRODMN0, YRAMOMN0, YUGRPMN0) reported those as barelineNosthat land on each host's own comment/DEFINE DATAlines. Root cause: the provenance was already on theREADS/WRITESedge (item 66 stampsoriginFile/viaCopycode/includedAton every edge inCopycodePreprocessor.remap, persisted viaSET r += e.properties), but theDB_ACCESSES/WORKFILE_ACCESSES/SQL_STATEMENTSqueries never returned it — exactly the gap item 85 closed forfunctionsand item 66 forcallees/variables. Fix (pure query + DTO, no re-parse of data):db-accessesandworkfile-accessesnow returnsites: [{lineNo, sourceFile, viaCopycode, includedAt}](newAccessSiterecord) alongside the keptlineNos;sql-statementsgainssourceFile+viaCopycodefrom the DB_ACCESS node's (remapped) file.includedAtis stored as a string, so the site queries wrap it intoInteger(...). Direct and transitive (?depth>0,*_FOR_MODULES) variants. REST auto-serializes the records; MCP returns the same DTOs; the CLI is a JSON passthrough — all in sync. ITWorkfileAndCopycodeFunctionIT#dbAccessSiteNamesTheCopycodeFileForCopycodeSourcedAccess(hostFINDvs copycodeFIND→sites[0].sourceFile/viaCopycodedistinguish the two). -
91.
search/identifier?priorityModule=pins the caller's module into the page; deterministic order (2026-07-26, UI click-to-identify test). Click-to-identify sentsearch/identifier?name=&limit=25, but the query had noORDER BYand paginated in incidental index order, so for a name declared in >25 modules (e.g.#I-LINE-LEV, 192 declarations) the open module's own declaration was truncated away and the popover falsely reported "0 in this module". Fix:SEARCH_IDENTIFIERgains$priorityModule— it does not filter (unlikemodule=) but computes apinRank(0 for that module's file, else 1) andORDER BY pinRank, sourceFile, startLine, so the local match survives thelimitwhile the global list is preserved; ordering is now deterministic (it was undefined before). Delivered across REST (priorityModule), MCPsearch_identifier, CLI--priority-module, and the UI hook. ITIdentifierPriorityModuleIT(four modules sharing one LOCAL field,PRIO_ZZZ_TARGETsorts last: excluded atlimit=2without the pin, first in the page with it, and the full set still returned at a large limit — i.e. no filtering). -
90.
DELETEno longer mis-parsed as a table write (2026-07-19, second WGEAGB0S deep API audit, Finding 5). Natural DMLDELETE [(label)]deletes the current record of the enclosing READ/FIND loop and names no view, and theEXAMINE … DELETE [FIRST]clause is not a DELETE statement at all — butDB_WRITEcaptured the token afterDELETEas a table, producing phantomFROM(108×, from SQLDELETE FROM <table>),(OLD.)/(*)/label refs (19×), andFIRST(7×) — and, for SQL, lost the real table (it sat afterFROM). Fix:DELETEremoved fromDB_WRITE(STORE/UPDATE keep their view operand); a newDB_DELETE_FROMcaptures the SQLDELETE FROM <table>real table (modeDELETE);DELETE (label)/DELETE FIRSTname no table. The same label-reference shape also affectsUPDATE (label)(UPDATE (OLD.)/(HOLD-PRIME.)) and a(*)read operand — a view never starts with(, soDB_WRITE/DB_READnow carry a(?!\\()guard that rejects a parenthesized label reference (no phantom(OLD.)/(*)table) whileUPDATE <view>still records the real view. Both parsers. Tests inNaturalParserTest(DELETE FROM,DELETE (OLD.),EXAMINE … DELETE FIRST,UPDATE (OLD.)). Follow-up (2026-07-19, final verification): one last(*)phantom survived, from the SQL SELECT parser, notDB_READ. A SELECT column list can contain a hyphenated Natural field whose last segment is literallyFROM(YCOMIROW.DAT-CALC-FROM (*));FROM_VIEW = \bFROM\s+(\S+)treated the hyphen as a word boundary, captured the trailing(*)as a phantom table, and — since the FROM view binds on the first match only — swallowed the realFROM VERSVW_COMISIONclause below. Fix:FROM_VIEWnow uses a negative lookbehind(?<![-\\w.])FROM(FROM must be a standalone SQL keyword, not an identifier tail) plus the(?!\\()operand guard. Both parsers. TestNaturalParserTest#sqlSelectColumnEndingInFromDoesNotShadowTheRealFromClause. -
89.
READ WORK <n>(FILE keyword omitted) recognized as work-file I/O (2026-07-19, second WGEAGB0S deep API audit, Finding 4). The item-84 guard only matchedREAD WORK FILE; the corpus also writesREAD WORK 1 ONCE RECORD …(289× project-wide) withoutFILE, which still fell through toDB_READand produced a phantomDB_TABLE 'WORK'(154 accesses). Fix: theWORK [FILE] nguard and theWORKFILE_ACCESS/WORKFILE_DEFINEpatterns now treatFILEas optional (guarded on a following digit, so a view whose name merely starts withWORKis unaffected). Both parsers;NaturalParserTest(READ WORK 1 ONCE RECORD). -
88. Finalize sweep deletes edgeless
DB_TABLE/WORKFILEplaceholder nodes (2026-07-19, second WGEAGB0S deep API audit). Companion to item 86: reaping a stale access edge left the placeholder node (sourceFile="", never node-swept) behind with degree 0 — invisible todb-accesses(edge-driven) but still surfacing insearch_identifier?type=DB_TABLEand the DB-table inventory (observed: orphanedNUMBER/WORK/FIRSTafter items 84/87). New finalize stepdelete-orphaned-placeholder-tables(DELETE_ORPHANED_PLACEHOLDER_TABLES) removes anyDB_TABLE/WORKFILEwith no relationships; degree-0 only, so a table any file still accesses (or a Java@Entity'sMAPS_TOtarget) is kept. Covered byStaleTableEdgeReapIT.orphanedPlaceholderTableNodeIsDeleted. -
87.
FIND NUMBER <view>no longer mis-parsed as a phantomDB_TABLE 'NUMBER'(2026-07-19, second WGEAGB0S deep API audit).FIND NUMBER <view>is a count-only FIND (natural-grammar.md §7.1);NUMBERis a statement keyword, not the accessed view — but theDB_READregex (in bothNaturalParserandNaturalCoarseScanner) captured it as the table name, sodb-accessesreported a bogusNUMBERtable and lost the real view (e.g.CON-DB2-AUTHPROF-USED-IN-AUTHSPC,NEXT-VIEW). Seen on 11 modules of the WGEAGB0S call tree (YAPRFMN0,YCUACMN0,YENTIMN0,YGARAMN0,YLITEMN0,YMODAMN0,YMTABMN0,YPRODMN0,YRAMOMN0,YTABLMN0,YUGRPMN0; 13 accesses; 182FIND NUMBERoccurrences project-wide). Same class as item 84'sREAD WORK FILE. Fix:DB_READnow skips the FIND optionsALL/FIRST/NUMBER/UNIQUEand theRECORDS/IN/FILEnoise words before the view (and allows a variable record-limit(operand), not just a literal). Covered byNaturalParserTest(FIND NUMBER MY_VIEW/FIND NUMBER IN FILE OTHER_VIEW). Item 86 reaps the existingNUMBERedges on the next refresh. -
86. Re-ingest reaps stale Natural
DB_TABLE/WORKFILEaccess edges (self-healing) (2026-07-19, follow-up to items 84/85). A statement whose access target changed between parses orphaned its oldREADS/WRITESedge forever: the target is a placeholder (sourceFile="", never node-swept) and the source node survives, so neither the item-58 node sweep nor the target-keyed edge MERGE reaped it. Seen as theWORKdb-access that lingered onUSIX052Nafter the item-84 parser fix (a pre-fixREAD WORK FILE→DB_TABLE 'WORK'edge), and it applies to any edited view name too. Fix: before re-merging a re-parsed Natural file's edges,DELETE_STALE_NATURAL_TABLE_ACCESS_EDGESdrops itsREADS/WRITESedges toDB_TABLE/WORKFILEplaceholders; the fresh parse (which always re-emits them) re-creates the current ones, unchanged ones round-trip identically. Scoped tolanguage:'natural'source nodes — Java DB edges are resolver-built (RESOLVE_JAVA_DB_ACCESS) and untouched. Covered byStaleTableEdgeReapIT(editREAD VERSVW_OLD→READ VERSVW_NEW+READ WORK FILE, assert old view +WORKgone). (A clean re-ingest ofupmsalready cleared the existing staleWORK; item 86 prevents recurrence on incremental refreshes.) -
84. Natural work-file access tracking (
workfile-accesses) +READ WORK FILEno longer a phantom DB table (2026-07-19, WGEAGB0S deep API audit, Finding 2).READ WORK FILE n <buf>(sequential flat-file I/O) was matched by the(READ|FIND) <view>DB pattern in bothNaturalParserandNaturalCoarseScanner, creating a bogusDB_TABLE 'WORK'READS access (seen onUSIX052Nin the WGEAGB0S call tree). BothDB_READpatterns now negative-lookaheadWORK FILE, andREAD/WRITE WORK FILEare modelled as first-classWORKFILE+WORKFILE_ACCESSnodes (analogue ofDB_TABLE/DB_ACCESS), keyed by work-file number, with the record buffer on theREADS/WRITESedge and theDEFINE WORK FILE n '<name>'physical name on the node. NewGET /modules/{name}/workfile-accesses→[{workFile, physicalName, mode, recordBuffers, lineNos}], MCPworkfile_accesses, CLIac workfile-accesses. Covered byNaturalParserTest(READ + WRITE) and full-stackWorkfileAndCopycodeFunctionIT;mcp-api-usage/system-prompt docs updated. -
85.
/functionsitems carrysourceFile+viaCopycode(copycode-provided subroutine provenance) (2026-07-19, WGEAGB0S deep API audit, Finding 1). A subroutine pulled into a module viaINCLUDEwas listed withdeclaredIn=the including module and the copycode'sstartLine/endLinebut no file, so the lines pointed outside the module's own (shorter) file — e.g.ISIN0019(59-line file) reportedGET-FORMATat 90–120, which actually live inISIC0010.cpy. The FUNCTION node already stored the rightsourceFile; theMODULE_FUNCTIONS/MODULE_FUNCTIONS_OWN/_INHERITEDprojections just dropped it. NowInheritedFunction/FunctionInfoexposesourceFile(+ derivedviaCopycode = f.sourceFile <> m.sourceFile). Covered byWorkfileAndCopycodeFunctionIT; docs updated. -
Module
callersdefault is external-only; noMODULEself-loop (2026-07-19, WGEAGB0S deep API audit). Two coupled defects inCypherQueries.callers(scope): (1) the top-level main body'sPERFORMs originate at theMODULEnode, soscope=internal/default reported the module as its own caller (WGEAGB0S → WGEAGB0S), a self-loopcalleesnever mirrors — fixed withAND caller <> mon the internal scope; (2) the default (scope=null) merged external callers with intra-module PERFORM wiring, so a module's own subroutines showed up as its "callers" — the default now maps toexternal(genuine incoming CALLNAT/inheritance only).context/digest(both callcallers(…, null)) inherit the clean view;scope=internalstill exposes function→function PERFORM wiring; callees unchanged. Covered byModuleCallersSelfLoopIT(fails 2/3 before the fix). MCPcallerstool description + REST endpoint doc +mcp-api-usage-ac-implementation.mdupdated. (Supersedes the earlier "Not a bug (verified):context.callersincludes internal PERFORM callers — noisy but accurate" note.) Ego-graphdirection=inforWGEAGB0Snow returns its dynamic callersW-LST-N0/W-MNT-N0(item 75). Payloaddirectionis alwaysREQUESTfor PDA-derived contracts (a single interface PDA doesn't encode direction) — a documented limitation, not a bug.- Follow-up (2026-07-19, WGEAGB0S call-tree closure re-audit): the "external-only" default was only
half-fixed. The default view still returned the calling
FUNCTIONnode (a subroutine/method) whenever the call originated inside a subroutine rather than the main body —ModuleCallersSelfLoopITmissed it because its fixture callerCALLNATs from the main body, where the edge already starts at theMODULE. Measured onupms: 46/56 modules in theWGEAGB0Sclosure hadFUNCTION-typed rows in the defaultcallers, 29/56 had duplicate rows, and hot utilities were unusable (CDRANGEdefaultcallers= 500 rows / 3 distinct FUNCTION names / 0 module callers). Fixed by making the external branch ofCypherQueries.callers(scope)roll every caller up to its owningMODULEvia(callerModule:MODULE)-[:CONTAINS*0..1]->(source)-[r]->(m)andcollect(DISTINCT …)— symmetric with howcalleesanchors its source side. Covered by newCallersRollupIT(caller invokes from inside a subroutine, twice → one rolled-up MODULE row with two aggregated sites, no FUNCTION leak); the oldModuleCallersSelfLoopIT,JavaWiringIT,JavaModulesExtendsFilterITstay green.
- Follow-up (2026-07-19, WGEAGB0S call-tree closure re-audit): the "external-only" default was only
half-fixed. The default view still returned the calling
Natural parser robustness
(Item 59 — shared field-declaration tokenizer — completed 2026-07-15; items 61 — comments parsed as
CALLNAT targets — 62 — data literals as false MODULE call targets — and 63 — CALLNAT matched inside a
string literal — completed 2026-07-16. All moved to x-docs/features.md. No open items remain in this
track: the unanchored-CALLNAT family (#61 comments / #62 data literals / #63 string literals) is
closed, and the shared lexical helpers now live in NaturalLines + NaturalFieldTokenizer so a fix
lands in both ingest tiers at once.)
Agent API / MCP tooling gaps
(Done items 52, 53, 54, 56 moved to x-docs/features.md.)
-
82. Manual override for unresolvable dynamic
CALLNATtargets (human/agent-settable) (proposed + implemented 2026-07-19, from the WGEAGB0S deep-API audit; REST + MCP +acCLI + Testcontainers ITs green). The dynamic-CALLNATresolvers cannot follow every name-assembly pattern — e.g.YGEAGGNH.nat:443 CALLNAT #GETSHORT-MODULwhere the name is built viaMOVE 'YGEAGKEY' TO #GETSHORT-MODUL+MOVE 'GN0' TO SUBSTR(#GETSHORT-MODUL,6,3)→YGEAGGN0(a real, ingested module). Such a call site leaves anunresolvedplaceholder (type=MODULE, sourceFile="", name = the variable). Add a REST + MCP +ac-CLI capability to list unresolved dynamic call sites and manually resolve a call site to one or more target modules (multiple targets = deliberate branches, each materialised as a realCALLScallKind=CALLNAT_DYNAMICedge, provenancemanual). Decisions (2026-07-19): callsite key =originFile + lineNo; overrides are persistent (own node type the refresh never deletes) and auto re-applied by an enrichment step after every refresh/deep-refresh; a reset endpoint clears manual overrides (one call site, or all). Related: this is the actionable counterpart to the Bug B consistency gap —callees/digestshould also surface theunresolvedflag thatgraphalready exposes. -
83. Auto-resolver for string-assembled dynamic
CALLNATtargets (SUBSTR/MOVE constant-folding) (proposed 2026-07-19, from the WGEAGB0S deep-API audit follow-up; implemented 2026-07-27). Many unresolved dynamic call sites are in fact statically foldable: the target name is built from literals only, e.g. theY…GNH"GetShort" family (~44 modules) —MOVE 'YxxxxKEY' TO #GETSHORT-MODUL+MOVE 'GN0' TO SUBSTR(#GETSHORT-MODUL,6,3)→YxxxxGN0— plus similar families (PXFRAA01.ST-PGMacross theP…MP0programs, theYFRAMBCK.cpy:27group). Add an enricher that constant-folds a chain ofMOVE <literal>andMOVE <literal> TO SUBSTR(var,pos,len)assignments feeding aCALLNAT varinto the effective target, and resolves the edge automatically when that target is a real ingested module — so item 82's manual override is only needed for genuinely runtime-dependent names, not the deterministic string idioms. Must respect precedence: a manual override (item 82) still wins over an auto-fold. Deliver with characterization ITs (a minimal fixture per idiom) + the usual REST/MCP/CLI-visible effect (fewerunresolvedsites; resolvedcallees/callers). Done:NaturalParserrecordsMOVE '<lit>' TO SUBSTR(var,pos,len)as aWRITESon the base var carryingsubstrPos/substrLen; newRESOLVE_DYNAMIC_CALLNAT_FOLD(+_SCOPED) folds base literal + ordered overlays (reduce/left/substring) → resolvedCALLSedge taggedfolded=true; the direct-literal/indirect/cross resolvers now skip partial-slice writes (substrPos IS NULL). Precedence honoured by guarding the fold against:DynamicCallOverridesites plus adelete-folded-overridden-dynamic-callnatstep beforeapply-manual. Characterization ITs inDynamicCallnatFoldIT(fold resolvesYABALKEY+GN0@6/3→YABALGN0; manual override wins); full dynamic-callnat regression 89/89 green. Docs inmcp-api-usage-ac-implementation.md. -
26. MCP session reliability (POSTPONED 2026-07-13) (investigated 2026-07-07, not fixed — see below) —
mcp__agenticcode__module_functions(and potentially other MCP tools) intermittently failed with"the first message from the client must be initialize: tools/call"after a sequence of prior successful MCP calls in the same session, forcing a fallback to the equivalent REST endpoint. Findings: no session/idle-timeout is configured server-side (quarkus.http.idle-timeoutunset), andapplication.propertiesonly setsquarkus.mcp.server.server-info.*. The error shape (client senttools/callwithout a freshinitialize) points at the MCP client's reconnect handling on a new SSE stream, not a server-side bug this codebase's config can fix. Left open pending either aquarkus-mcp-server-sseversion bump with related fixes, or evidence this is in fact server-triggered.