Repository navigation
Code Analysis Call Evidence
This is the producer slice of the local Code Analysis experiment, prepared as Draft PR 1225 on top of #1220. It records callsites and conservative lexical targets in raw-facts.json. It does not add a call graph, question CLI, paired investigation or UI. The work remains outside Labs/dev until reviewed and merged.
Candidate: bfb7a5d56986db5222291630ad4f1159c77db317. Comparison base: 06dd7340c44fc2bebafbbb897b86f9f789d303a1. Both use TypeScript 5.9.3.
| Fixed source scope | Source question and observed result |
|---|---|
| Archify Labs bf0a29e7, full root | The call in schema.mjs:23 points to the directly exported semanticErrors at semantics.mjs:3. The entry contains 14 captured CallExpressions. |
| EventEmitter3 a6505491, full root | Registration calls at 228 and 241 point to addListener at 58. All 14 calls inside emit remain unresolved: 12 invoke stored listeners with .fn.call/.fn.apply, and 2 invoke a removal method. Captured coordinates do not identify which callback object executes. See the independent dispatch investigation. |
| Redux c9e06506, src only | Compatibility control: all pre-existing import, file and function records remain unchanged. |
| dependency-cruiser e6517db0, full root | Same preservation control. Ten test-fixture parse errors are unchanged; this full-root scope is broader than the earlier source-only resolver comparison. |
Across these scopes, the parent and candidate preserve all existing facts except extraction identity and the new call evidence. The normal CLI and unpacked archive agree byte for byte. The counts below describe captured output, not independently checked binding accuracy across every call.
| Scope | Files | Imports | CallExpressions | Resolved lexical targets | Unresolved sites |
|---|---|---|---|---|---|
| Archify | 607 | 3,447 | 112,145 | 27,593 | 84,552 |
| EventEmitter3 | 16 | 87 | 1,085 | 6 | 1,079 |
| Redux src | 17 | 39 | 87 | 27 | 60 |
| dependency-cruiser | 1,157 | 1,972 | 12,876 | 1,406 | 11,470 |
callModel: "js-ts-lexical-v1" identifies call collection independently of adapter revision 8. A local, unpublished question consumer previously inferred capability from revision numbers. An import-only revision 6 producer was consequently described as having available call coverage with zero records. That consumer has a separate local fix requiring an explicit model; the producer PR does not publish the question workflow.
The model collects CallExpressions from parse-valid, non-declaration files. It binds ordinary declarations, direct const function initializers and relative named imports to direct captured exports. Other dispatch remains explicit unknown. Constructors and tagged templates are excluded. A target is a lexical declaration hint, not proof of execution, reachability or complete impact.
Old empty arrays remain valid in the updated schema. Missing/unknown models do not establish capture capability. Older strict schemas reject the new marker. Consumers must also account for parse errors, excluded files and declaration files.
This evidence has a storage cost: the Archify raw-facts output grew from 15,476,327 to 42,493,249 bytes. No speedup or reader-benefit claim is made. The parser, snapshot host and resolver are reused; no dependency was added.
Eleven of 13 call tests fail on the exact parent; two negative controls already pass. All 24 focused tests and 246 module tests pass on the candidate. The archive was built twice with official Node 22.23.2 and has SHA256 ded08e6909ee9fcee3242396b54d182698edd10f0c2ccba385219b746043cc04. Remote CI is recorded on the Draft; local results do not establish merge readiness.
Fresh public-fetch reproduction passed with Node 22.23.2. Requires Git, unzip and Node 22. The script fetches fixed public commits and installs locked dependencies only in temporary checkouts. It checks the two source questions plus parent preservation and source/archive parity. It does not execute the analyzed applications.
#!/usr/bin/env bash
set -euo pipefail
w=$(mktemp -d)
printf '%s\n' "$w"
pin() {
git init -q "$w/$1"
git -c http.version=HTTP/1.1 -C "$w/$1" fetch -q --no-tags --depth=1 "https://github.com/$2.git" "$3"
git -C "$w/$1" checkout -q --detach FETCH_HEAD
}
pin analyzer tt-a1i/archify bfb7a5d56986db5222291630ad4f1159c77db317
pin parent tt-a1i/archify 06dd7340c44fc2bebafbbb897b86f9f789d303a1
pin source tt-a1i/archify bf0a29e7664f52973d06a8e6a608df2db1ecbd39
pin eventemitter3 primus/eventemitter3 a650549176ded0d0dcc52e27b03ae5669d7dcd7c
unzip -q "$w/analyzer/archify.zip" -d "$w/unpacked"
for tool in analyzer parent unpacked; do
(cd "$w/$tool/archify/modules/code-analysis" && npm ci --ignore-scripts --no-audit --no-fund)
for project in source eventemitter3; do
node "$w/$tool/archify/bin/archify.mjs" code-analysis check "$w/$project" \
--language ts --fail-on never --out "$w/$tool-$project-out" --json > "$w/$tool-$project-receipt.json"
done
done
node - "$w" <<'JS'
const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path');
const w=process.argv[2];
const read=(tool,project)=>JSON.parse(fs.readFileSync(path.join(w,tool+'-'+project+'-out','raw-facts.json')));
const preserved=({calls,callModel,repository:{adapterRevision,snapshotId,...repository},...facts})=>({repository,...facts});
for(const project of ['source','eventemitter3']) {
const current=read('analyzer',project),parent=read('parent',project),packaged=read('unpacked',project);
assert.deepEqual(current,packaged);
assert.deepEqual(preserved(current),preserved(parent));
assert.equal(current.callModel,'js-ts-lexical-v1');
assert.equal(current.repository.adapterRevision,8);
if(project==='source') {
const calls=current.calls.filter(c=>c.file==='archify/modules/code-analysis/extract/shared/schema.mjs');
assert.equal(calls.length,14);
const call=calls.find(c=>c.line===23&&c.target?.name==='semanticErrors');
assert.equal(call.resolution,'imported-function');
assert.deepEqual(call.target,{file:'archify/modules/code-analysis/extract/shared/semantics.mjs',name:'semanticErrors',line:3,column:1});
} else {
const registration=current.calls.filter(c=>c.file==='index.js'&&[228,241].includes(c.line));
assert.equal(registration.length,2);
assert.ok(registration.every(c=>c.resolution==='same-file-function'&&c.target.name==='addListener'&&c.target.line===58));
const dispatch=current.calls.filter(c=>c.file==='index.js'&&c.line>=166&&c.line<=216);
assert.equal(dispatch.length,14);assert.ok(dispatch.every(c=>c.resolution==='unresolved'));
const lines=fs.readFileSync(path.join(w,'eventemitter3','index.js'),'utf8').split('\n');
assert.equal(dispatch.filter(c=>/\.fn\.(call|apply)\(/.test(lines[c.line-1])).length,12);
}
console.log(project,{files:current.files.length,imports:current.imports.length,calls:current.calls.length,previousFactsUnchanged:true,archiveParity:true});
}
JS
Draft PR 1226, candidate a16d94f78d2ae6b0b92d2e3785781d7e1944554a, adds the consumer on top of #1225. task --question accepts a JS/TS implementation entry and an optional function name without a finding. The output is orientation-only: it supplies source identity, imports, scope and bounded call records for the host Agent to investigate, not a generated semantic answer.
Six source questions were checked through the normal CLI and unpacked archive:
| Fixed input | Expected source evidence and observed result |
|---|---|
| Archify Labs schema.mjs | 14 calls for the file; 4 in schemaErrors; the named import points to semanticErrors at semantics.mjs:3. |
| Same source, older #1220 facts | Entry source still matches, but call coverage is unavailable and total is null. The explicit call model prevents revision 6's empty array from becoming a known zero. |
| EventEmitter3 registration |
on contains one captured call to addListener at line 58. |
| EventEmitter3 dispatch | All 14 calls inside emit stay unresolved, including its 12 stored-listener calls. No particular runtime listener is inferred. |
| Redux isPlainObject.ts | The function spans 5–14 and contains three Object.getPrototypeOf source callsites, all unresolved member dispatch. A loop can execute a source site multiple times; these are not execution counts. |
The old local workflow rejected TypeScript entries despite already-captured TS facts. This layer accepts .js, .mjs, .cjs, .jsx, .ts, .mts, .cts and .tsx. Declaration files remain explicitly unsupported. Both the body and JSON receipt expose call coverage; a captured empty function remains available/0. Entry freshness determines whether the CLI returns 0 or 1, so exit 0 does not certify complete call coverage or a completed analysis.
Forty focused and 264 module tests pass. The tests preserve finding export/verification and cover changed entry/target navigation, parse errors, omitted and nested calls, real empty coverage, historical unknown models, TS/JSX entries, declaration rejection and entry-output protection. Archive orientation agrees after normalizing the location of its bundled workflow document. Canonical archive SHA256: cd4fe0089410abcc28790d112345354c5ced069bb043c29a70dbc26714a556d2.
Current navigation checks source text and coordinates; it does not re-resolve imports after other files/configuration change. Only 40 call records are projected; saved facts and scope metadata are not subject to that same limit. Paired change analysis and JSON inventory remain separate local work. CI status and limitations are recorded on the Draft.
Fresh public-fetch reproduction passed with Node 22.23.2. The following script uses fixed public commits, installs dependencies only in temporary checkouts, and checks the six cases and source/archive parity without executing the analyzed applications.
#!/usr/bin/env bash
set -euo pipefail
w=$(mktemp -d)
printf '%s\n' "$w"
pin() {
git init -q "$w/$1"
git -c http.version=HTTP/1.1 -C "$w/$1" fetch -q --no-tags --depth=1 "https://github.com/$2.git" "$3"
git -C "$w/$1" checkout -q --detach FETCH_HEAD
}
pin analyzer tt-a1i/archify a16d94f78d2ae6b0b92d2e3785781d7e1944554a
pin import_only tt-a1i/archify 06dd7340c44fc2bebafbbb897b86f9f789d303a1
pin source tt-a1i/archify bf0a29e7664f52973d06a8e6a608df2db1ecbd39
pin eventemitter3 primus/eventemitter3 a650549176ded0d0dcc52e27b03ae5669d7dcd7c
pin redux reduxjs/redux c9e06506f88926e252daf5275495eba0c04bf8e3
unzip -q "$w/analyzer/archify.zip" -d "$w/unpacked"
for tool in analyzer import_only unpacked; do
(cd "$w/$tool/archify/modules/code-analysis" && npm ci --ignore-scripts --no-audit --no-fund)
done
for project in source eventemitter3 redux; do
root="$w/$project"
if [ "$project" = redux ]; then root="$root/src"; fi
node "$w/analyzer/archify/bin/archify.mjs" code-analysis check "$root" \
--language ts --fail-on never --out "$w/$project-out" --json > "$w/$project-receipt.json"
done
node "$w/import_only/archify/bin/archify.mjs" code-analysis check "$w/source" \
--language ts --fail-on never --out "$w/old-out" --json > "$w/old-receipt.json"
node - "$w" <<'JS'
const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),{spawnSync}=require('node:child_process');
const w=process.argv[2],schema='archify/modules/code-analysis/extract/shared/schema.mjs';
const cases=[
['schema-file','source','source-out',schema,null,14],
['schema-function','source','source-out',schema,'schemaErrors',4],
['old-facts','source','old-out',schema,null,null],
['registration','eventemitter3','eventemitter3-out','index.js','on',1],
['dispatch','eventemitter3','eventemitter3-out','index.js','emit',14],
['typescript','redux/src','redux-out','utils/isPlainObject.ts','isPlainObject',3],
];
for(const[name,root,analysis,entry,symbol,total]of cases){
const results=[];
for(const tool of ['analyzer','unpacked']){
const output=path.join(w,tool+'-'+name+'.json');
const args=[path.join(w,tool,'archify/bin/archify.mjs'),'code-analysis','task',path.join(w,analysis),'--question','Which source sites support this path, and what remains unresolved?','--entry',entry,'--root',path.join(w,root),'--format','json','--out',output,'--json'];
if(symbol)args.push('--function',symbol);
const run=spawnSync(process.execPath,args,{encoding:'utf8'});assert.equal(run.status,0,run.stderr||run.stdout);
const receipt=JSON.parse(run.stdout),brief=JSON.parse(fs.readFileSync(output));
assert.equal(brief.stage,'orientation-only');assert.equal(brief.entry.factsStatus,'valid');
assert.equal(brief.calls.total,total);assert.equal(receipt.calls.total,total);
assert.equal(receipt.calls.coverage,total===null?'unavailable':'available');
if(total===null){assert.equal(receipt.calls.model,null);assert.deepEqual(brief.calls.records,[]);}
if(name.startsWith('schema-')){
const target=brief.calls.records.find(x=>x.fact.target?.name==='semanticErrors');
assert.equal(target.targetNavigation.status,'valid');assert.equal(target.targetNavigation.location.line,3);
assert.equal(target.targetNavigation.location.file,'archify/modules/code-analysis/extract/shared/semantics.mjs');
}
if(name==='registration'){assert.equal(brief.calls.records[0].fact.target.name,'addListener');assert.equal(brief.calls.records[0].targetNavigation.location.line,58);}
if(name==='dispatch'||name==='typescript')assert.ok(brief.calls.records.every(x=>x.fact.resolution==='unresolved'));
if(name==='typescript'){assert.equal(brief.entry.location.startLine,5);assert.equal(brief.entry.location.endLine,14);}
const {workflow,...normalized}=brief;results.push(normalized);
}
assert.deepEqual(results[0],results[1]);console.log(name,{coverage:results[0].calls.coverage,total,archiveParity:true});
}
JS
Draft #1227 adds js-ts-lexical-v2 with direct relative default function declarations and retains v1 saved-question support. Six preselected Redux callsites now point to their source declarations; EventEmitter3 and Archify Labs controls are unchanged. Source expectations, boundaries and executed reproduction. This is a separate coverage extension; the v1 results above retain their original revision and meaning.
Draft #1229 adds a fresh before/after question for one required JS/TS entry on top of #1227. Source answers and executed reproduction distinguish a public export, a behavior fix, formatting and unsupported declaration analysis. Member/callback dispatch remains unresolved; zero supported references is not zero callers. This slice is unmerged and does not include automatic entry selection or JSON inventory.
The current producer follow-up withdraws a compiler-symbol false target for valid JavaScript parameter defaults. Current #1225/#1226 use v1 / revision 10; #1227/#1229 use v2 / revision 11. The model contracts remain unchanged. Earlier results on this page retain their recorded source pins; the follow-up lists final revisions, focused/module checks and source/archive/public reproduction.