CLAUDE.md · diff

git:20260822.e864ec4 to git:20260925.8725496

9 added, 2 removed. Audit A to A.

# ISA Toolkit — Claude Code Context
## Repository
- **Repo:** `andremun/InstanceSpace` (MATLAB, R2025a+)
- **Owner:** sole developer (Mario Andrés Muñoz) — no human reviewers; GitHub Copilot is the automated PR review gate. `master` has branch protection requiring changes go through a PR.
- **Primary API:** the `InstanceSpace` class (`InstanceSpace.m`) — `build()`/`explore()`/`save()`/`InstanceSpace.load()`/`getResults()`/`plot()`. `buildIS.m`/`exploreIS.m` are thin backward-compatibility wrappers around it (kept for callers like the MATILDA web platform); don't add new logic to them directly.
- **Completed refactor plan:** `docs/tech/isa_refactor_plan_v1.7.pdf` — historical source of truth for the v0.9.0 refactor (all ten phases). Now fully shipped; treat it as an archival reference, not a live backlog.
- **Current backlog:** GitHub Issues are the live source of truth. Every `v0.9.1`-milestone issue is now resolved and merged to `master` (see "Current state" below) — what's left is scoped for a **v0.9.2** milestone that doesn't exist as a GitHub milestone *object* yet (no milestone-management tool has been available in this environment across several sessions now; #30/#50/#52/#53 are the filed items, each needs its milestone field set by hand once v0.9.2 exists).
- **Reference papers:** `docs/papers/Smith-Miles2023.pdf` (general ISA methodology), `docs/papers/Simpson2025.pdf` (ISA3, the 3D extension — cited only in `PILOT.m`, `PILOTviewpoint.m`, `TRACE.m`, and the 3D-output files in `output/`, not repo-wide). PILOT's origin (Muñoz et al. 2018), legacy TRACE's origin (Muñoz & Smith-Miles 2017), and FILTER's origin (Alipour et al. 2023) are cited in each file's own docstring `Reference:` block but aren't archived as PDFs here.
## Test harness
Run from the repo root; `buildIS.m`/`exploreIS.m` print `EOF:SUCCESS` on success (errors abort before printing), and `test_integration.m` prints `EOF:SUCCESS`/`EOF:ERROR` at the end:
example
test_integration
`example.m` already runs the full `buildIS`+`exploreIS` pipeline internally on the bundled reference dataset — it's the one to start with. `test_integration.m` is a thin `matlab.unittest`-based runner (#39) over `tests/*.m` (`PipelineOptionsTest` — every classifier, tuning strategy, 2D/3D, PLS, viewpoint groups, parameterised; `ClassApiTest` — staged `build()`/`explore()`/save-load round-trips; `MigrationTest` — the full `ISAmigrateModel` legacy-migration table; `RegressionTest` — targeted fixed-bug regressions), with a `CodeCoveragePlugin` producing `coverage.xml`. New features and bug fixes get a test *method* under `tests/`, not a new block in `test_integration.m` itself. Good as a reference for how an option is meant to be used, not the place to start reading. Runs automatically on every push/PR via CI (#34, `.github/workflows/tests.yml`); pinned to `release: R2025a`, not `latest` (deliberate — see the `matlab-ci-testing` skill below for why).
## Current state (v0.9.0 and v0.9.1 both shipped to `master`; v0.9.2 not started)
- **v0.9.0** (July 2026): all ten refactor-plan phases — rewritten PRELIM/SIFTED/PILOT/PYTHIA/TRACE pipeline, the `InstanceSpace` class API, `ISAmigrateModel`/`ISAvalidateOpts`, 3D projection/viewpoint support, and a licence change from GPL v3 to PolyForm Noncommercial 1.0.0.
- **v0.9.1** (August 2026): engineering-quality, architecture, and infrastructure follow-ups — no algorithmic changes, no new user-facing options, per that milestone's own scope rule. Landed on `master` via three merged PRs; **`RELEASE_NOTES.md`'s v0.9.1 section is the full changelog** (organised into New functionality/Better engineering/Bug fixes) — this file only tracks what a future session specifically needs to know, not a duplicate list:
- **#51**: the bulk of the engineering work (INIT.m/PRELIM train-eval dual mode, per-stage contract validation and `onStage` inspection callbacks, CI, `matlab.unittest` migration, several confirmed correctness fixes). Went through **five rounds** of Copilot review; each round found genuine bugs — including, more than once, in a fix landed earlier in the *same* PR (e.g. an added safety warning that didn't actually fire; a guard that proved two values matched each other but not that either was still correct). If touching any of that code again, don't assume it's settled just because it's merged — check the PR's review-comment history first.
- **#54**: added the `matlab-ci-testing` Claude Code skill (`.claude/skills/matlab-ci-testing/`) — CI setup, `matlab.unittest` architecture, MATLAB-specific test-writing patterns, distilled directly from the #51 review cycle. Also added Codecov and GitHub-downloads badges to `README.md`.
- **#55**: `README.md` had accumulated internal development-process narration (issue numbers, "confirmed via CI", references to a prior implementation, an internal spec-section reference) — removed; see "Documentation conventions" below for the standing rule this established. Same PR also fixed a repo-wide docstring/doc-formatting audit: README's ~208 inline-code spans were triple-backtick instead of single; `core/PYTHIA.m` was the one `core/` pipeline file missing the `Inputs`/`Outputs` docstring section its siblings all have; a couple of files had inconsistent `%---` divider-comment widths.
- **Not yet done, not code**: the Codecov badge shows no data until the repo owner links the repo at codecov.io and adds a `CODECOV_TOKEN` GitHub Actions secret — the CI-side upload step is already in place (`fail_ci_if_error: false`, so its absence can't break CI).
- **v0.9.2 backlog** (filed, none started, no milestone object yet — see "Current backlog" above): #30 (PILOT projection-orientation canonicalisation), #50 (CLOISTER's convex hull is 2D-only even for a 3D projection), #52 (`traceOneRegion` doesn't trace a hole's boundary cycle), #53 (a MATLAB-toolbox-style reference documentation site — `doc InstanceSpace` Help-browser integration via `info.xml`/`helptoc.xml` plus GitHub Pages, ~29 pages; full scope is in the issue body). Unlike v0.9.1, several of these (#30/#50/#52) are explicitly algorithmic and #53 is a new deliverable — don't assume v0.9.1's "no algorithmic changes" rule carries over to v0.9.2 by default; check with the user.
- **Closed as not planned**: a cross-repo reference-fixture export tool for the Python port (`pyInstanceSpace`, #35) — per a standing preference, anything dealing with the Python port is handled from that repo, not this one.
- **Closed as resolved, no code change**: #45 (verify `PRELIM.m`'s `boxcox()` call) — confirmed it resolves to the Financial Toolbox's `boxcox.m`, the same dependency CI already installs for this call site; combined with the already-verified positive-shift guard, no gap was found.
- **Repository layout**: `core/` (INIT, PRELIM, SIFTED, PILOT, PILOTviewpoint, CLOISTER, PYTHIA, TRACE, TRACE_legacy, FILTER), `output/` (scriptcsv, scriptpng, scriptweb, scriptfcn, ISArecallView), `utils/` (ISAdefaults, ISAvalidateOpts, ISAgetClassifierFcn, ISAmigrateModel, ISAsubsetData), `deprecated/` (PYTHIA2, PYTHIAtest, SIFTED2 — warn-and-forward shims kept for backward compatibility only; don't build new code on these), `.claude/skills/` (`instance-space-analysis` — ISA methodology plus this repo's operational reference; `matlab-ci-testing` — MATLAB CI/`matlab.unittest` setup and debugging, not ISA-specific, reusable in other MATLAB repos).
## Workflow
- Work on feature branches; merge to `master` after Copilot review. `master`'s branch protection requires a PR — don't push directly to it.
- Fix root causes, not symptoms — no `real()` wrappers, no `isfinite()` guards.
- Cite the relevant GitHub issue number in commit messages (e.g. `#38`) for issue-tracked work, not a refactor-plan section — the plan document is closed out; issues are the live backlog.
- A docs-only change (no `.m` files) doesn't need the same review scrutiny as a code change — #54/#55 both merged directly once CI was green, no extended review cycle.
## Key conventions
- Language: MATLAB; style follows standard MATLAB conventions.
- No magic numbers — use named `opts` fields with documented defaults (`ISAdefaults.m`), not hardcoded literals.
- Standalone pipeline functions (`PRELIM`, `SIFTED`, `PILOT`, `CLOISTER`, `PYTHIA`, `TRACE`, `FILTER`) must remain callable independently of the `InstanceSpace` class — deliberate, not an oversight, to preserve MATILDA web-platform compatibility. Don't fold them into class methods.
- **Train vs. eval mode is dispatched via `nargin`, not a fixed positional argument**: an optional trailing `trainedModel`/`trainedTrace`/`trainedPrelim`-style struct as the *last* argument selects eval mode over train mode (e.g. `PYTHIA(Z,Y,Ybin,Ybest,algolabels,opts)` trains; `PYTHIA(...,opts,trainedModel)` evaluates; same shape for `TRACE` and, since #38, `PRELIM`/`INIT`). Confirmed by direct audit (see #38) that `SIFTED`/`PILOT`/`CLOISTER`/`FILTER` never recompute anything at explore time — they just reuse a stored trained artifact directly (e.g. `PILOT`'s trained projection is reused via a plain `X*A'` multiply) — so they don't need this structure and shouldn't get it added speculatively.
- **Data ingestion (#38)**: `INIT.m` (`core/`) does the CSV-read + `opts.selvars.feats`/`.algos` filtering that used to be duplicated between `InstanceSpace.runPrelim` and `InstanceSpace.evaluateTestSet` — `INIT(rootdir, opts)` trains (reads `metadata.csv`), `INIT(rootdir, opts, trainedModel)` evaluates (reads `metadata_test.csv`, validates the feature set/order against `trainedModel`, reconciles algorithm columns). `PRELIM.m` itself now also has the `nargin` dual mode: its Ybin/Ybest/P/beta computation (including tie-breaking) is unconditional shared code, only the bound-clipping/Box-Cox+Z-score normalisation branches on train (fit) vs eval (apply trainedPrelim's already-fit parameters).
- `opts.pilot.topoWeight` — still reserved/disabled, no effect in the current version; don't wire it up without confirming it's actually in scope for the task at hand.
## Documentation conventions
- **`README.md` is user-facing only.** No GitHub issue numbers, no "confirmed via CI", no "an earlier version did X", no internal spec-section references (`spec §N`) — state the current fact, not how it got that way. That kind of context belongs in `RELEASE_NOTES.md`, `CLAUDE.md`, or commit messages/issues instead. (This was found drifting in during v0.9.1 and cleaned up in #55 — don't let it drift back in.)
- **Inline code in Markdown docs uses single backticks** (`` `like this` ``), not triple backticks. `README.md` had drifted to ~208 triple-backtick inline spans before #55's cleanup; `CONTRIBUTING.md`/`SECURITY.md`/`CLAUDE.md`/`RELEASE_NOTES.md` were already consistent. Triple backticks are still correct for actual fenced code blocks.
- **Every `.m` file's docstring** starts with the same copyright/SPDX-license header block (copy an existing file's, e.g. `core/PILOT.m`) followed by a `Reference:` section citing only the paper(s) that specific file implements. `core/` pipeline-stage files (`PRELIM`/`SIFTED`/`PILOT`/`PILOTviewpoint`/`CLOISTER`/`PYTHIA`/`TRACE`/`TRACE_legacy`/`FILTER`/`INIT`) and `InstanceSpace.m` additionally use a structured `Inputs`/`Outputs` section (see any of them for the exact format); `utils/`/`output/`/`deprecated/`/`buildIS.m`/`exploreIS.m` intentionally use lighter narrative-prose docstrings instead — that split (algorithm implementations vs. supporting utilities) is deliberate, not an inconsistency to "fix."
## Known deferred items (tracked, not stale — check the linked issue before touching)
- - **CLOISTER's boundary computation is 2D-only, even for a 3D projection** — `core/CLOISTER.m`'s `Zedge`/`Zecorr` use `convhull(Z(:,1),Z(:,2))` (the 2-argument form), silently ignoring the 3rd coordinate whenever `opts.pilot.dims==3`, with no warning. `InstanceSpace.plot('boundary')`/`scriptpng.m`'s boundary PNG both refuse to render an inaccurate 3D boundary already (differently: `scriptpng.m` silently skips the file, `plot('boundary')` raises `ISA:InstanceSpace:boundaryNot3D`), but CLOISTER's own hull is still 2D-only either way. Filed as #50; algorithmic change, deferred to v0.9.2.
- - **`traceOneRegion` doesn't trace a hole's boundary cycle within a single alphaShape region** — a region with a hole (e.g. an annulus) is one region by `numRegions`'s count but has two disconnected boundary cycles (outer ring + hole); the tracer only follows the cycle containing its arbitrary starting vertex. Warns (`ISA:scriptfcn:boundaryHoleOmitted`) instead of silently dropping the hole, but the hole itself is still omitted from the traced output. Filed as #52; algorithmic addition, deferred to v0.9.2.
+ - **#30** (PILOT projection-orientation canonicalisation) is the remaining open algorithmic item.
+ - **Resolved on `claude/docs-testing-improvements-amt87s` (v0.9.2 work, pending merge):** #50 (CLOISTER now builds a 3D hull with `ZedgeFaces`/`ZecorrFaces`; `plot('boundary')`/`scriptpng` draw it; a 3D model built before the fix still raises `ISA:InstanceSpace:boundaryNot3D`), #52 (`traceOneRegion` traces every boundary cycle, NaN-separated; the `boundaryHoleOmitted` warning is gone), #58 (PYTHIA eval scores only rows with observed `Y`; unobserved algorithms get `NaN` metrics), #59 (Oracle `Probability_of_good` = `mean(any(Ybin,2))`), #57 (JUnit report + Codecov Test Analytics).
+
+ ## Documentation site (#53)
+ - Sources: `doc/src/*.md`; generator: `doc/generate.py` (python-markdown, pinned in `doc/requirements.txt`); output: `doc/html/` (committed — the MATLAB Help browser reads it via `info.xml` from a plain clone).
+ - The `TOC` list in `doc/generate.py` is the single source for the sidebar, breadcrumbs, `helptoc.xml`, the generated *All Functions* page and search. A page not in `TOC` fails the build.
+ - Page conventions (Syntax/Description/Examples/Input Arguments/Output Arguments/Version History/See Also, ``### `name` — Short description`` argument headings, `<!-- opts: pilot -->` for bare `opts.x` links) are documented in the generator's docstring.
+ - `.github/workflows/docs-pages.yml`: `generate.py --check` + `.github/scripts/check_doc_links.py` on PRs; deploys `doc/html` to GitHub Pages from `master`. Pages must be enabled once in the repo settings (Source: GitHub Actions) — not something this environment can do.
+ - When a function's behaviour or signature changes, update its `doc/src` page (and a Version History entry), then regenerate.
**LIBSVM MEX-files removed (#29).** `svmpredict.mexw64`/`svmtrain.mexw64` were precompiled binaries with no corresponding source in the tree — removed entirely rather than kept-with-provenance, since LIBSVM is already fully deprecated for new runs. `PYTHIA`'s eval mode now raises a clear `ISA:PYTHIA:noLibsvm` error (naming the algorithm and pointing to the official LIBSVM project) if it ever needs to dispatch to a legacy LIBSVM-format classifier struct and `svmpredict` isn't on the path, instead of MATLAB's generic undefined-function error. Don't re-add these binaries; if LIBSVM support is ever needed again, get it from upstream rather than re-committing a precompiled MEX-file with no visible build source.
## Cross-repo note
The user has asked (in this session) about a repository called `claude-skills` (owner `andremun`) twice — once too ambiguously to act on, once explicitly as `andremun/claude-skills`, at which point cloning it failed: this session's GitHub access (both the MCP tools and git-level credentials) is hard-scoped to `andremun/InstanceSpace` only, confirmed by a 403 on an unauthenticated API probe and `git clone` refusing to prompt for credentials. If a future session has broader repo access and this comes up again, that's the blocker that was hit — not a transient error worth silently retrying.
## `.gitignore` notes
- `docs/**/*.pdf` — tracked (negation rule), recursive under `docs/`.
- `test/data/*` — ignored except `metadata.csv`, `metadata_test.csv`, and `workspace_test.mat` (an explicit three-file allowlist). Do not broaden this to `!test/data/**` — that was tried and reverted because it makes every generated test-run output file (model.mat, CSVs, PNGs, per-case subdirectories from `test_integration.m`) trackable.