diff --git a/AGENTS.md b/AGENTS.md
index 904ce06..9a0a160 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -66,6 +66,7 @@ dev.hgh.jai.editor.JaiEditorSupportTest tests=7
dev.hgh.jai.editor.JaiFoldingBuilderTest tests=3
dev.hgh.jai.findusages.JaiFindUsagesTest tests=2
dev.hgh.jai.highlighting.* tests=8
+dev.hgh.jai.inspection.JaiUnresolvedModuleInspectionTest tests=5
dev.hgh.jai.lexer.JaiCorpusLexerTest tests=2 <- the Tier 0 gate
dev.hgh.jai.lexer.JaiLexerTest tests=13
dev.hgh.jai.parser.JaiCorpusParserTest tests=1 <- the Tier 3 gate
@@ -78,7 +79,7 @@ dev.hgh.jai.structure.JaiStructureViewTest tests=3
dev.hgh.jai.formatter.JaiFormatterTest tests=4
dev.hgh.jai.settings.JaiProjectSettingsTest tests=3
dev.hgh.jai.settings.JaiConfiguredRootTest tests=6
--> total 99, failures+errors 0
+-> total 104, failures+errors 0
```
The corpus gates report what they actually did; check both lines are still there:
@@ -134,6 +135,10 @@ Tier 3: parsed 714 files, 714 clean (100.0%), 0 with errors, 0 PsiErrorElements
block indentation, opaque `#asm` preservation, and a corpus-wide idempotence
gate. Headless formatter tests cover registration, representative formatting,
opaque/directive token preservation, and all 714 corpus files.
+- **Phase 7a** — unresolved `#import`/`#load` module and file path inspection,
+ reusing the existing reference resolver. Headless tests cover unresolved
+ imports and loads, resolved project/local modules and files, and
+ `#import,string` exclusions.
### Lexer design facts worth knowing before touching it
@@ -202,14 +207,14 @@ Tier 3: parsed 714 files, 714 clean (100.0%), 0 with errors, 0 PsiErrorElements
### In progress — pick up here
-1. **Phases 7+** — inspections, compiler integration, and other optional work
- remain. `#asm` bodies are intentionally consumed opaquely, so nothing inside
- them has PSI yet.
+1. **Phases 7+** — additional inspections, quick fixes, live templates, and
+ compiler integration remain. `#asm` bodies are intentionally consumed
+ opaquely, so nothing inside them has PSI yet.
### Next planned increment
-- Decide whether to continue with inspections and compiler integration phases
- (7–8).
+- Expand Phase 7 with additional high-confidence inspections and quick fixes;
+ struct-field resolution and completion remain a separate known gap.
### Open questions for the user (unanswered)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 166cc42..0d682fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@
- PSI-aware formatter with operator and punctuation spacing, block indentation,
directive-flag handling, and preservation of opaque `#asm` bodies; formatting
is idempotent across all 714 corpus files.
+- Inspection warnings for unresolved `#import` and `#load` module/file paths.
### Known gaps
diff --git a/README.md b/README.md
index 03ec3ff..2d1fdab 100644
--- a/README.md
+++ b/README.md
@@ -28,10 +28,11 @@ never from online material (see `AGENTS.md`).
- PSI-aware formatting for operator and punctuation spacing, block indentation,
directive flags, and opaque `#asm` bodies; formatting is idempotent across the
full local corpus
+- Inspection warnings for unresolved `#import` and `#load` module/file paths
Remaining code-insight gaps include completion and symbol resolution for struct
-fields and other type-qualified members. Inspections and compiler integration
-are planned later.
+fields and other type-qualified members. Additional inspections and compiler
+integration are planned later.
See `docs/BUILD_PLAN.md` for the phase plan.
## Install it locally
diff --git a/docs/BUILD_PLAN.md b/docs/BUILD_PLAN.md
index f06f539..feb2761 100644
--- a/docs/BUILD_PLAN.md
+++ b/docs/BUILD_PLAN.md
@@ -172,7 +172,7 @@ Each phase has a machine-checkable gate. Do not advance without a green gate.
| **5** | Completion (keywords, directives, module names, visible declarations, parameters, module aliases, incomplete-expression recovery), rename, find-usages | Tier 4 fixture tests — **done** (84-test suite) |
| **5a** | Configurable Jai module/import roots and indexed external search scope | Tier 4 settings, resolution, completion, navigation, and find-usages tests — **done** (93-test suite) |
| **6** | Formatter, code style settings | Formatter round-trip — **done**, formatting all 714 corpus files is idempotent |
-| **7** | Inspections (e.g. `#must` misuse), quick fixes, live templates | Tier 4 + `verifyPlugin` |
+| **7** | Inspections, quick fixes, live templates | Tier 4 + `verifyPlugin` — initial unresolved `#import`/`#load` path inspection implemented |
| **8** | Optional: run-configuration to invoke the `jai` compiler, parse its error output | Integration test against `~/.local/jai/bin` |
Phase 6's idempotence check (format twice, assert no change) is another
@@ -218,6 +218,6 @@ currently a committed milestone.
setup, works today), or install a system JDK via Homebrew? I'd default to the
wrapper.
-Phases 0–6 are implemented and verified headlessly. The next decision is
-whether to continue with the optional inspections and compiler integration work
-in Phases 7–8.
+Phases 0–6 are implemented and verified headlessly. Phase 7 has an initial
+unresolved `#import`/`#load` path inspection; additional inspections and
+compiler integration remain optional follow-up work.
diff --git a/src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt b/src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt
new file mode 100644
index 0000000..eb8accd
--- /dev/null
+++ b/src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt
@@ -0,0 +1,45 @@
+package dev.hgh.jai.inspection
+
+import com.intellij.codeInspection.LocalInspectionTool
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.psi.PsiReferenceService
+import dev.hgh.jai.lexer.JaiTokenTypes
+import dev.hgh.jai.psi.JaiDirectiveExpr
+import dev.hgh.jai.psi.JaiLiteralExpr
+
+/** Reports file and module paths in #import/#load directives that do not resolve. */
+class JaiUnresolvedModuleInspection : LocalInspectionTool() {
+ override fun getDisplayName(): String = "Unresolved Jai import or load"
+
+ override fun buildVisitor(
+ holder: ProblemsHolder,
+ isOnTheFly: Boolean,
+ ): PsiElementVisitor =
+ object : PsiElementVisitor() {
+ override fun visitElement(element: PsiElement) {
+ val literal = element as? JaiLiteralExpr ?: return
+ val directive = literal.parent as? JaiDirectiveExpr ?: return
+ val directiveName = directive.node.findChildByType(JaiTokenTypes.DIRECTIVE)?.text ?: return
+ if (directiveName != "#import" && directiveName != "#load") return
+
+ val reference =
+ PsiReferenceService
+ .getService()
+ .getContributedReferences(literal)
+ .firstOrNull()
+ ?: return
+ if (reference.resolve() != null) return
+
+ val path = literal.text.removeSurrounding("\"")
+ holder.registerProblem(
+ reference.element,
+ "Cannot resolve $directiveName path '$path'",
+ ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
+ reference.rangeInElement,
+ )
+ }
+ }
+}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 1dc18eb..f5b8220 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -49,6 +49,13 @@
+
diff --git a/src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt b/src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt
new file mode 100644
index 0000000..5140b82
--- /dev/null
+++ b/src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt
@@ -0,0 +1,47 @@
+package dev.hgh.jai.inspection
+
+import com.intellij.testFramework.fixtures.BasePlatformTestCase
+
+class JaiUnresolvedModuleInspectionTest : BasePlatformTestCase() {
+ override fun setUp() {
+ super.setUp()
+ myFixture.enableInspections(JaiUnresolvedModuleInspection::class.java)
+ }
+
+ fun testUnresolvedLoadPathIsReported() {
+ myFixture.configureByText(
+ "main.jai",
+ "#load \"missing.jai\";",
+ )
+
+ myFixture.checkHighlighting()
+ }
+
+ fun testUnresolvedImportPathIsReported() {
+ myFixture.configureByText(
+ "main.jai",
+ "#import \"missing-module\";",
+ )
+
+ myFixture.checkHighlighting()
+ }
+
+ fun testResolvedLoadPathIsNotReported() {
+ myFixture.addFileToProject("other.jai", "value := 1;")
+ myFixture.configureByText("main.jai", "#load \"other.jai\";")
+
+ myFixture.checkHighlighting()
+ }
+
+ fun testResolvedImportPathIsNotReported() {
+ myFixture.configureByText("main.jai", "#import \"Basic\";")
+
+ myFixture.checkHighlighting()
+ }
+
+ fun testImportStringIsNotReportedAsAnUnresolvedPath() {
+ myFixture.configureByText("main.jai", "#import,string \"inline code\";")
+
+ myFixture.checkHighlighting()
+ }
+}