Diagnosing Jule Import and Symbol Visibility Failures After Refactoring
A diagnostic guide for Jule import failures and undefined symbols after refactoring, with a cause table, ordered checks, fixes tied to findings, and escalation criteria.
30 Nov 2025, 17:54 UTC

Code compiles here but fails after moving files
A recognizable condition in Jule is a build that succeeds in one directory or branch and then produces unresolved-import or undefined-symbol errors after moving files, adding imports, or switching build context. The code looks correct, but the compiler cannot locate the package or see the symbol.
The useful takeaway is to treat package boundaries as API boundaries. Keep the public surface small, colocate implementations with their types, and add a package-level smoke build so path or visibility regressions fail fast.
Recognizable condition
The failure appears after a structural change, not after a logic change. Local builds pass, CI fails, or a file that compiled in its original folder fails once imported from a sibling package. The error points to an import string or a name that is used in the source but not found by the compiler.
Cause and diagnostic table
| Symptom | Likely cause | Diagnostic action |
|---|---|---|
| unresolved-import | Wrong package or module path, file outside declared package | Compare import string with directory layout and package declaration |
| undefined-symbol | Symbol not visible outside package | Locate declaration and check export rule |
| Ambiguous or colliding name | Duplicate names across imports | List aliases and qualified uses |
| Intermittent local vs CI | Stale artifacts or toolchain flag drift | Clean rebuild and compare compiler version and flags |
Ordered checks
- Reproduce with a clean build. Run from the project root with write permission to delete build artifacts. Risk is loss of cached outputs. Expected check is a repeatable failure with the same import or symbol.
- Resolve the failing import to a concrete path. Confirm the import string matches the filesystem location and the package declaration at the top of the file.
- Find the declaration and confirm package and visibility. Open the source file that defines the symbol. Verify the package name matches the directory expectation and that the symbol is exported for external use.
- Check aliases and duplicate names. List all imports in the file and look for two packages exporting the same name. Use qualified references to disambiguate.
- Compare compiler version and flags. Print the toolchain version locally and in CI. Package resolution, visibility rules and standard library layout can change between releases.
- Reduce to one import and one symbol. Create a minimal file that imports only the failing package and references only one exported name.
Fixes tied to findings
Correct the import path or move the file into the intended package when the path does not match the layout. Re-run the original failing command after the move.
Export the symbol deliberately or add a thin public wrapper when visibility is the issue. In Jule a symbol must be marked for external visibility. Example:
// project/math/calc.ju
package math
func secret(a int) int { return a * 2 }
export func Add(a, b int) int { return a + b }
secret is internal to the package, Add is visible outside. Do not broaden exports without reviewing the public API impact.
Rename or alias to remove collisions. Use import aliases and qualified names so call sites are unambiguous.
Delete build artifacts and rebuild when stale outputs are suspected. Align toolchain and flags or add the missing platform constraint when behavior differs by environment.
Escalation criteria
Escalate only when a clean minimal repro still fails, when behavior differs only by toolchain version, or when evidence points to a compiler or stdlib defect. Include the exact command, versions, minimal code, expected result and actual output. Do not escalate style or naming preferences.
Limitations and verification
Package resolution and visibility are version sensitive. Pin the toolchain in CI and verify assumptions against the installed compiler before changing project structure.
Practical verification is a two-package repro where one package declares a symbol and another imports it. Confirm the failure disappears when the path and visibility are corrected. Run the same clean build locally and in CI with the toolchain version printed.
Add a smoke target that imports each public package and touches one exported symbol and run it on every pull request. Example:
// tests/smoke.ju
package tests
import "project/math"
import "project/util"
func TestSmoke() {
_ = math.Add(0, 0)
}
This fails fast when a package boundary or export is broken.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.