Skip to content

Release v0.4.1 — unified JSON format, parallel analyze, MCP convert/schema tools#10

Merged
lionelle merged 12 commits into
mainfrom
mcp
Jun 4, 2026
Merged

Release v0.4.1 — unified JSON format, parallel analyze, MCP convert/schema tools#10
lionelle merged 12 commits into
mainfrom
mcp

Conversation

@lionelle

@lionelle lionelle commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Release v0.4.1

Additive release — no breaking changes from 0.4.0. Bumps the version, adds the 0.4.1 CHANGELOG entry, and documents the features that landed on mcp since 0.4.0.

Highlights

  • Unified JSON degree format — author/analyze degrees as JSON as well as YAML; every degree subcommand auto-detects the format and converts raw ai-landscape JSON on the fly.
  • degree convert — ai-landscape program JSON → unified JSON (cluster pipeline files expand to one unified file per program).
  • degree schema — emit the unified-degree JSON Schema.
  • Parallel degree analyze (-j/--jobs, default 8) — process-isolated worker pool; a pathological degree can't take down the batch. --school rolls metrics up into one report. Plus scripts/analyze-batch.sh for an externally throttled (ulimit + timeout) variant.
  • JSON input for degree trim — round-trips the input format.
  • Metrics-rich report JSON — degree-first layout with per-plan course schedules.
  • New MCP toolsconvert_degree and get_degree_json_schema; existing degree tools now accept unified/ai-landscape JSON and the cache:<hash> handle.

Fixes

  • OOM on large select pools — bounded combination generation (cap 2000, deterministic down-sample); peak memory on worst catalogs drops from >6 GB to ~25–120 MB.
  • Converted programs collapsed to one plan — electives are now enumerated, restoring metric spread.
  • JSON parse errors report as a distinct JsonError.

Docs

CHANGELOG (0.4.1 entry), Readme (version + features), docs/degree.md (convert/schema/-j/--school/JSON format), docs/mcp.md (the two new tools + JSON-input note).

See CHANGELOG.md for the full entry.

🤖 Generated with Claude Code

lionelle and others added 12 commits June 2, 2026 09:56
…port

Add a unified JSON degree format (the DegreeProgram model serialized to JSON)
plus tooling around it:

- `degree convert`: convert ai-landscape program JSON to the unified format —
  maps category lists + picklists to requirements, flips their AND-of-OR
  prerequisites into our PrereqExpr tree, defaults missing credits (3) with
  warnings. Expands ai-landscape *cluster* pipeline files
  (course_verifier/course_scraper.<program>.results) into one unified file per
  program with collision-safe `<school>__<program>.unified.json` names.
- JSON as an input type alongside YAML (auto-detects + converts ai-landscape
  shapes on load); extension dispatch across the degree subcommands.
- Prerequisites as a symmetric tagged structure ({"and"|"or":[...]}, bare
  string = leaf) via serde on PrereqExpr.
- `tags` on Degree/Requirement/Course generalize ai-landscape's ai_program and
  fixed category names.
- Metrics-rich report JSON (degree- and course-level stats + variations /
  sample_type) and a school-level rollup (`--school`).
- `degree schema`: emit the unified-degree JSON Schema (src/assets/degree.schema.json).
- New DegreeParseError::JsonError so JSON inputs no longer report "YAML Parse Error".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ysis

Run `degree analyze` as one OS process per file, JOBS at a time, each with a
virtual-memory cap (ulimit -v) and a timeout. A pathological degree — e.g. a
full-catalog scrape with thousands of courses — aborts itself under the cap
instead of letting the OS OOM-killer take down the whole batch; the failure is
logged and the run continues. Threads can't provide this isolation because they
share one address space, so surviving an OOM requires separate processes.

Usage:
  scripts/analyze-batch.sh <input-dir|glob> <metrics-dir> [jobs] [-- extra flags]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…isolated per file

A multi-file `degree analyze` now runs as a rolling pool of worker processes
(default 8; `-j/--jobs N`), each analyzing one file in its own OS process. A
pathological degree (e.g. a full-catalog scrape with thousands of courses) is
contained to its own process: if it OOMs/crashes, the kernel kills only that
child, the parent records it in <metrics-dir>/failures.log, and the rest carry
on. Single-file, `--school`, `-j 1`, and worker-mode invocations run in-process
with full per-degree output.

The parent writes the index.csv header up front so concurrent workers only
append rows (atomic under O_APPEND), avoiding a header race. Worker stdout/stderr
is suppressed; the parent prints a progress line + summary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carry each failed worker's exit status into the failures.log (one
`path<TAB>status` line), so an OOM kill shows `signal: 9 (SIGKILL)`
and is distinguishable from a non-zero exit. Extract the reap loop into
`reap_finished` to cut nesting, add a `DEFAULT_ANALYZE_JOBS` constant,
a `metrics_dir_or_default` helper, and a `WORKER_POLL` const.

Document that `analyze_child_flags` must mirror every result-affecting
flag in args.rs, and that the pool deliberately has no ulimit/timeout
(unlike scripts/analyze-batch.sh).

Add 10 tests: analyze_child_flags coverage incl. a clap round-trip,
reap_finished, metrics_dir_or_default, write_index_csv_header, and a
-j 1 in-process integration test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`degree trim` previously filtered to YAML only. It now accepts unified
JSON (and raw ai-landscape JSON) via the existing degree-input filter, and
writes the trimmed program back in the input's format: a `.json` input
yields a trimmed `.json`, YAML stays YAML.

Add `save_degree_auto` to mirror `load_degree_auto` (extension dispatch,
path-carrying error) and route `trim_one` through it. Update the two
non-YAML trim tests for the new degree-filter messages and add three
integration tests: JSON round-trips to JSON, a mixed YAML+JSON batch keeps
each format, and the overwrite guard covers JSON inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Findings from analyzing the converted cluster_outputs dump: two NuAnalytics
engine root causes (eager C(N,k) combination materialization causing the 35
OOM/SIGKILL failures; `ENUMERABLE_CATEGORIES = ["major"]` collapsing every
converted program to variations_run=1) plus ai-landscape data issues
(ambiguous picklist [N] count-vs-credits yielding 316 impossible
requirements, unparseable picklist tags, whole-catalog scrapes, missing
course_hours, scraped/validated naming divergence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two engine fixes that made the converted-catalog analysis usable:

Bug A (OOM): RequirementResolver materialized every C(n,k) combination of a
select pool. A pool like "choose 15 of 42" (~10^11) allocated tens of GB and
the OS OOM-killed the worker — even for an otherwise tiny 78-course program.
generate_combinations is now routed through bounded_combinations, which
down-samples to MAX_MATERIALIZED_COMBINATIONS (2000) distinct combinations when
C(n,k) exceeds the cap (combinations_exceed computes the bound without
overflow; sample_combinations is deterministic). Peak memory on the worst
catalog programs drops from >6 GB to ~25-120 MB.

Bug B (variations_run=1): ENUMERABLE_CATEGORIES was ["major"], so every
elective-category select was excluded from the plan space and converted
programs produced a single plan with std_dev=0. It now includes "elective";
estimate_plan_count uses saturating multiplication since a capped pool can
still push the nominal product past usize::MAX. American University CS goes
from variations_run=1 to ~4760 with real metric spread.

Add unit tests for combinations_exceed/sample_combinations/bounded_combinations
(incl. k==0, k==n, determinism, cap) and for elective-category enumeration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Output JSON now opens with the degree block. A new unified_value_to_string
serializes converted files as degree, requirements, courses, conversion_warnings
(nested objects keep serde_json's sorted, deterministic order); the unified
report is laid out degree, analysis, requirements, selected_plans, courses.

Each selected plan now carries its courses: credits, course_count, the critical
path, and a term-by-term schedule mirroring the MCP analyze_degree shape — so a
reader can see exactly which courses the shortest path contains. total_credits
already exists on the Degree model and surfaces at the top (populated during the
correction pass when the source omits it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tools

Add two MCP tools and make the existing degree tools accept the unified
(and raw ai-landscape) JSON format, not just YAML:

- parse_degree_auto: content-level format sniff (`{`/`[` -> JSON loader with
  ai-landscape auto-convert; else YAML). validate/analyze/audit/trim/
  course_detail now route through it, so unified/ai-landscape JSON content
  (and the convert tool's cache:<hash> handle) work everywhere. validate
  surfaces conversion_warnings.
- convert_degree: ai-landscape program JSON -> unified JSON + warnings; a
  cluster file returns a bounded program inventory (or converts one via
  the `program` selector). Caches the result for chaining by degree_id.
- get_degree_json_schema: returns the machine JSON Schema (the same
  degree.schema.json the CLI emits).

Also fix the schema's `from` clause: it was an untyped object, so wildcard
pools weren't documented. Added a `fromClause` definition covering
courses/pattern/include/exclude/groups with wildcard examples ("CS:2500+",
"*:*") -- confirming the unified format supports the same wildcard
gen-ed/elective pools as YAML.

Verified via MCP stdio smoke (tools register; schema + convert respond;
convert->cache->validate chaining works). +12 unit tests; full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onvert/schema tools

Version bump 0.4.0 -> 0.4.1 plus documentation for the additive features
landed since 0.4.0:

- CHANGELOG: 0.4.1 entry (unified JSON degree format, `degree convert` /
  `degree schema`, parallel/process-isolated `analyze -j`, `--school`
  rollup, JSON input for `trim`, `convert_degree` / `get_degree_json_schema`
  MCP tools, the bounded-combinations OOM fix and elective-enumeration fix).
- Readme: version line + new feature bullets.
- docs/degree.md: convert/schema command sections, `-j/--jobs` and
  `--school` options, a Parallel Analysis section, and a unified-JSON
  input-format note.
- docs/mcp.md: `get_degree_json_schema` and `convert_degree` tool docs, and
  an input-formats note covering YAML/unified/ai-landscape JSON across the
  degree tools.

No code changes; manifest verified via `cargo read-manifest`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's `cargo clippy --all-features -D warnings` (a newer clippy than last
green) flagged `self.m2 += delta * delta2` as clippy::suboptimal_flops.
Use the suggested fused `delta.mul_add(delta2, self.m2)` — one rounding
instead of two, so slightly more accurate, and it clears the lint that
was failing every PR. No behavioral change to the streaming statistics.

Verified: `cargo clippy --all-targets --all-features -- -D warnings` is
clean locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lionelle lionelle merged commit 340cbfc into main Jun 4, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant