@inklyre/oes-lint
v0.2.0
Published
Referential-integrity linting for OES content: dangling references, duplicate ids, pool select bounds, answer cross-references, answer_key file existence, and content_hash mismatches.
Readme
@inklyre/oes-lint
Referential-integrity linting for OES content, built on
@inklyre/oes-core's resolve() rather than reimplementing a
second tree-walker.
Install
npm install -D @inklyre/oes-lintCLI
npx oes lint ./my-course
npx oes lint ./my-set --entry set.json
npx oes lint ./my-course --json # machine-readable output, e.g. for a GitHub ActionExits 0 when no "error"-severity issue is found, 1 otherwise. The
entry document (course.json/set.json/module.json/lesson.json) is
auto-detected from the given directory unless --entry is passed.
GitHub Action
Since OES content lives in git, lint-on-every-PR is a near-zero-friction way to keep a course/set repo trustworthy. Any repo can use this one's composite action without installing anything first:
# .github/workflows/lint.yml, in a course/set repo (not this one)
on: pull_request
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: inklyre/oes/.github/actions/lint@main
with:
path: ./my-courseWhat it checks
Everything resolve() already catches while walking the tree:
- Dangling references — a
path/*_urlthat doesn't resolve, or a document that fails schema validation. content_hashmismatches — a declared hash that doesn't match the referenced document's actual content.
Plus checks that need a whole-tree or single-document view resolve()
doesn't attempt on its own:
- Duplicate ids — within any array addressed by
id:course.modules, a module'slessons, a lesson'sarticles/video_lessons/practice_sets, a set'squestions, a pool'sfrom, and every question type's own id-bearing array (options,left/right,items,blanks,test_cases,labels). - Pool
selectbounds — a pool'sselectmust be between 1 andfrom.length; not expressible in JSON Schema since it relates two sibling fields. - Dangling answer cross-references — an
mcq/msqanswer(s), amatchpair'sleft_id/right_id, or anorder'scorrect_orderpointing at an id that doesn't exist in the corresponding option array. Only checked in self-practice mode: a securedanswer_keymeans there's no inline answer to check. answer_key.fileexistence — checked directly by this package, not byresolve(), which deliberately never fetchesanswer_keycontent at all: a public/student-facing consumer built onresolveCourse/resolveSetmust never gain a path to the answer through the shared resolver.
Programmatic API
import { fsSource } from "@inklyre/oes-core";
import { lintCourse } from "@inklyre/oes-lint";
const { issues, ok } = await lintCourse(fsSource("./my-course"));lintSet, lintLesson, and lintModule are the equivalent entry points
for linting a smaller subtree in isolation (e.g. a set-only repo, or an
authoring tool previewing one lesson).
What this doesn't do
- Grading, or anything answer-key-content-aware beyond existence — this package checks the file exists, never what's inside it.
- Anything
resolve()doesn't already cover as a side effect of walking the tree — this package adds checks, it doesn't re-implement reference resolution.
