| title | m-stdlib — discoverability & tooling implementation tracker | |
|---|---|---|
| status | live (2026-05-08; Wave A not yet started; all rows = not-started) | |
| authority | this file is the canonical "what's done / in flight / queued" view for the discoverability & tooling plan. Any commit that touches a task below MUST update the row's Status and append to the task's narrative section in the same commit. | |
| plan | docs/plans/discoverability-and-tooling-plan.md (the design doc; this tracker drives the work items) | |
| sibling | docs/tracking/module-tracker.md (the module-level tracker; deferred decisions D1/D2/D3 there originated in the same plan) | |
| audience | anyone executing or proposing work against the discoverability plan, including AI agents asked to "complete phase X" | |
| created | 2026-05-08 | |
| last_modified | 2026-05-10 | |
| revisions | 22 | |
| doc_type |
|
This tracker drives the work in
docs/plans/discoverability-and-tooling-plan.md.
The plan is the why and what; this file is the who/when/where.
Tasks are grouped into four waves (Wave A → D) matching the plan's § 8 phasing. A wave's downstream consumers (m-cli, VS Code, AI skills) depend on its upstream artefact (the manifest from Wave A), so the order matters; within a wave, most tasks can run in parallel unless a Deps cell says otherwise.
When asked "based on the tracking table, complete phase X" (e.g. "complete Wave A"), follow this procedure:
- Read the plan section matching the phase. Each Wave maps to a plan § — e.g. Wave A is plan § 3 + § 8 Wave A. The task rows below include the precise plan-§ pointers.
- Walk the tracker rows for the requested wave. For each row
whose Status is
not-startedorin-progress:- Verify every Dep listed is
done. If a Dep is not done, work that Dep first (or block on it). - Read the narrative section for that Task ID further down this file. The narrative carries any prior progress notes, blockers, and design choices already pinned.
- Read the file(s) listed in the Paths column. The plan § pointer in Notes gives the design rationale.
- Verify every Dep listed is
- Execute the task following project conventions (TDD-first,
make check,m fmt,m lint --error-on=error, per-module acceptance gate frommodule-tracker.md§ "Per-module acceptance gate"). For tasks landing in m-cli rather than m-stdlib (Wave B), the work lands in~/projects/m-cli/and m-stdlib remains untouched. - Update this tracker in the same commit:
- Flip the row's Status (
not-started→in-progress→done). - Append a dated bullet to the task's Progress log in the narrative section, summarising what landed, the commit SHA(s), and any follow-ups.
- If the task is
done, set the row's Status, fill in the Tag column with the m-stdlib release that carries it (or "n/a" for m-cli/VS Code/skill artefacts), and check the Acceptance condition is genuinely met.
- Flip the row's Status (
- Cross-tracker hygiene. If the work also lands a module-level
change (e.g. backfilling tags inside
src/STDxxx.mis also a change to that module), add a one-line note inmodule-tracker.mdTable 1. The discoverability tracker stays focused on the cross-cutting metadata/tooling work; per-module behaviour stays in the module tracker.
Everything below is the canonical pointer set an agent needs to do the work without further orientation.
| Concern | Path |
|---|---|
| Plan / design doc | docs/plans/discoverability-and-tooling-plan.md |
| Module tracker (sibling) | docs/tracking/module-tracker.md |
| Toolchain findings | docs/tracking/TOOLCHAIN-FINDINGS.md |
| Released-module catalogue | docs/modules/index.md |
| Per-module long-form docs | docs/modules/*.md |
| User guide | docs/guides/users-guide.md |
| TDD guide | docs/guides/m-tdd-guide.md |
| M routines (the source of truth for the manifest) | src/STD*.m |
| Tests | tests/STD*TST.m |
| Build / generator scripts (target dir for new tools/) | tools/ |
| Manifest output (will not exist until WA4 lands) | dist/stdlib-manifest.json |
| Errors output (will not exist until WA7 lands) | dist/errors.json |
| Release notes | changelog.md |
| Project conventions | CLAUDE.md |
| Concern | Path |
|---|---|
| m-cli source (Wave B target) | ~/projects/m-cli/ |
| m-cli command source dir | ~/projects/m-cli/src/cmd/ (existing pattern; new doc.m, search.m, manifest.m, examples.m, errors.m land here) |
| m-cli lint rules dir (WA3 target) | ~/projects/m-cli/ (find lint subdir; rule M-DOC-001 lands alongside other M-DOC-* rules if any, or as a new family) |
| tree-sitter-m parser (informs WA4 design; do NOT bind to it for Wave A) | ~/projects/tree-sitter-m/ |
| AI skill output dir (Wave D) | ~/claude/skills/m-stdlib/ |
| AI memory home | ~/claude/memory/ |
| VS Code extension scaffold (does not yet exist; Wave C creates it) | ~/projects/m-stdlib-vscode/ (recommended new repo) or ~/projects/m-cli/vscode/ (if shared) — decide in WC1 |
| Concern | Pointer |
|---|---|
| JSDoc tag spec (informs WA1 grammar) | https://jsdoc.app/ |
| rustdoc reference (informs WA1, search-index design) | https://doc.rust-lang.org/rustdoc/ |
godoc (informs WB1 m doc UX) |
https://go.dev/doc/comment |
Status legend. not-started · in-progress · blocked · done · deferred (tracked elsewhere; left in the table only as a pointer).
Phase legend. WA = Wave A (m-stdlib: grammar + manifest). WB = Wave B (m-cli: m doc family). WC = Wave C (VS Code extension). WD = Wave D (AI surface).
Repo legend. stdlib = ~/projects/m-stdlib/ · m-cli = ~/projects/m-cli/ · vscode = VS Code extension repo (TBD in WC1) · skill = ~/claude/skills/m-stdlib/.
| Task ID | Status | Phase | Seq | Repo | Title | Deps | Effort | Acceptance | Plan § | Paths |
|---|---|---|---|---|---|---|---|---|---|---|
| WA1 | done | WA | 1 | stdlib | Specify M-doc tag grammar (@param / @returns / @raises / @example / @since / @stable / @see / @deprecated / @internal) and synopsis-line rule |
— | 0.5d | New docs/guides/m-doc-grammar.md reviewed; sample worked example shown using STDJSON.parse |
§ 3.1 | docs/guides/m-doc-grammar.md (landed 2026-05-08) |
| WA2 | done | WA | 2 | stdlib | Backfill structured doc tags across all public labels in src/STD*.m (incl. @stable tier annotation per § 3.6) |
WA1 | 3–4d | Every public label in src/ has @params matching the formal-list, @returns for extrinsics, @raises for any $ECODE it sets, ≥1 @example, @since, @stable. Manifest generator (WA4) parses src/ without diagnostics. |
§ 3.1, § 3.6 | src/STD*.m (all 32 routines); ALL DONE (32/32) |
| WA3 | done | WA | 3 | m-cli | Add lint rule M-DOC-001 warning on public labels missing required tags (warn-only at first) |
WA1 | 0.5–1d | m lint raises M-DOC-001 (severity warn) on a public label whose ; doc: block is missing @returns (extrinsic) or has a @param not in the formal-list. No errors emitted yet. |
§ 3.1 (acceptance gate) | ~/projects/m-cli/src/m_cli/lint/_doc.py (new), ~/projects/m-cli/tests/test_lint_doc.py (new), m-cli commit f97b849 pushed |
| WA4 | done | WA | 4 | stdlib | Implement tools/gen-manifest.py — hand-rolled parser — that walks src/STD*.m and emits dist/stdlib-manifest.json + dist/errors.json per the § 3.2 schema |
WA1 | 1–2d | Running make manifest produces dist/stdlib-manifest.json; all public labels present with signature, source.file/line, plus tag-derived fields (params, returns, raises, examples, since, stable, see_also) populated when WA2 backfill provides tags. |
§ 3.2, § 11.4 | tools/gen-manifest.py (new, ~370 LoC), dist/stdlib-manifest.json (new, generated), dist/errors.json (new, generated), Makefile (manifest + manifest-check targets) |
| WA5 | done | WA | 5 | stdlib | CI gate: regenerate manifest in CI, fail on diff against committed dist/stdlib-manifest.json |
WA4 | 0.5d | CI workflow runs make manifest-check; passes today, fails if a label changes without manifest re-gen. |
§ 3.2 (acceptance gate) | .github/workflows/ci.yml (step add), Makefile (already had manifest-check) |
| WA6 | done | WA | 6 | stdlib | Add YAML frontmatter (module / tag / phase / stable / since / synopsis / errors / labels / conformance / see_also) to every docs/modules/stdXXX.md |
— | 0.5d | Every per-module markdown has frontmatter parsing cleanly as YAML. Field values agree with the manifest where overlapping (verified by a small check script, not gated yet). | § 3.3 | docs/modules/std*.md, tools/write-module-frontmatter.py (new), Makefile (frontmatter target) |
| WA7 | done | WA | 7 | stdlib | Generate dist/errors.json (inverted index of U-STD* codes → producing module + labels) as a derivative of the manifest |
WA4 | 0.25d | make manifest also writes dist/errors.json containing every U-STD* code with its origin module + labels. |
§ 3.5 | tools/gen-manifest.py (covers WA7 too), dist/errors.json (generated) |
| WA8 | done | WA | 8 | stdlib | Cut a release tag carrying Wave A; update docs/modules/index.md to link the manifest + errors registry |
WA4, WA5, WA6, WA7 | 0.5d | A new tag (≥ v0.5.0 or per CHANGELOG decision) ships with manifest + errors.json + frontmatter; docs/modules/index.md carries a "Machine-readable surface" subsection linking both. |
§ 8 Wave A gate | CHANGELOG.md ([v0.5.0] heading), docs/modules/index.md (Machine-readable surface section), git tag v0.5.0 (local; not pushed) |
| WB1 | done | WB | 1 | m-cli | m doc <symbol> — module overview, single-label, fuzzy lookup; reads dist/stdlib-manifest.json from the resolved m-stdlib install at runtime |
WA4 | 1–2d | m doc STDJSON, m doc STDJSON.parse, and m doc parse all return correct godoc-style output within ~100ms cold. |
§ 4.1 | ~/projects/m-cli/src/m_cli/doc/{cli,lookup,format}.py (new+rewrite), ~/projects/m-cli/tests/test_cli_doc_lookup.py (new), m-cli commit 0024a72 pushed |
| WB2 | done | WB | 2 | m-cli | m doc --json and m doc --short flags |
WB1 | 0.5d | --json emits the raw manifest entry; --short emits one-line synopsis. Both stable for scripting. |
§ 4.1 | landed in the same commit as WB1 (0024a72) — format.py carries format_*_short and format_*_json formatters; cli.py wires the flags |
| WB3 | done | WB | 3 | m-cli | m search <query> — full-text fuzzy search over manifest synopsis + description + example |
WA4 | 0.5–1d | m search "json parse" returns ranked module.label — synopsis lines; matches against case-insensitive substrings in synopsis, description, examples. |
§ 4.2 | ~/projects/m-cli/src/m_cli/doc/search.py (new), m-cli commit 6542e73 pushed |
| WB4 | done | WB | 4 | m-cli | m manifest, m examples <module>, m errors thin wrappers |
WA4, WA7 | 0.5d | All three commands emit useful output piped from the manifest / errors registry. | § 4.3, § 4.4, § 4.5 | ~/projects/m-cli/src/m_cli/doc/{manifest,examples,errors}.py (all new), m-cli commit 6542e73 pushed |
| WC1 | done | WC | 1 | vscode | VS Code extension v0.1: hover, goto-def, completion driven by dist/stdlib-manifest.json (no LSP) |
WA4 | 2–3d | Extension activates on .m files; hover on ^STDJSON and parse^STDJSON shows synopsis + signature; goto-def jumps to src/STDJSON.m:L; completion suggests ^STD* modules and labels. |
§ 5.1 | new repo rafael5/m-stdlib-vscode initial commit 1c15a05 pushed |
| WC2 | done | WC | 2 | vscode | Snippet pack for canonical patterns: STDASSERT suite skeleton, STDFIX with wrapper, STDLOG kv line, STDJSON parse-then-walk |
WC1 | 0.5d | Typing stdassert-suite (etc.) in a .m file expands to the canonical idiom. |
§ 5.1 | rafael5/m-stdlib-vscode snippets/m.json + package.json contributes.snippets; commit 7d0723b pushed |
| WD1 | done | WD | 1 | skill | AI skill at ~/claude/skills/m-stdlib/ — SKILL.md + manifest-index.md + patterns.md + error-codes.md, generated from the manifest |
WA4 | 1d | Skill files exist; tools/gen-skill.py regenerates them deterministically; make skill-check gates drift. |
§ 6.1 | tools/gen-skill.py (new), tools/skill-patterns.md (new static input), dist/skill/*.md (generated, committed), Makefile (skill / skill-check / skill-install targets), ~/claude/skills/m-stdlib/*.md (installed live), ~/claude/CLAUDE.global.md (skill registry entry) |
| WD2 | done | WD | 2 | stdlib | Doctest generator tools/gen-doctests.py — emits tests/STDxxxDOCTST.m from self-contained Pattern-A @example lines so doc examples must execute |
WA1, WA2 | 1d | 15 generated suites, 51/51 doctests pass under m test; make doctest-check gates drift; full make test (47 suites, 2562 assertions) green. |
§ 3.4 | tools/gen-doctests.py (new), tests/STD*DOCTST.m (15 files, generated + committed), Makefile (doctest / doctest-check / doctest-run targets) |
| Tracker ref | Subject | Status |
|---|---|---|
module-tracker.md D1 |
HTML / GitHub Pages docs site (plan § 5.2) | deferred — revisit when a non-maintainer adopts m-stdlib or a second contributor lands a module |
module-tracker.md D2 |
@stable SemVer CI gate (manifest-diff at HEAD vs last tag) |
annotation ships in WA2; gate deferred — revisit when v1.0 is being planned or a non-maintainer consumer adopts m-stdlib |
module-tracker.md D3 |
Replace hand parser in tools/gen-manifest.m with tree-sitter-m |
hand parser ships in WA4; revisit when tree-sitter-m hits stable v1 or generator needs language features beyond labels + doc comments |
Each Task ID has a section below. Keep narratives short during planning; expand them as work happens. The format is:
### <Task ID> — <short title>
**Status.** <not-started | in-progress | blocked | done>
**Goal.** One paragraph from the plan section.
**Approach.** Bullets — how to attack this.
**Acceptance.** Same string as the table cell, expanded if useful.
**Out of scope.** Anything to deliberately not do here.
**Progress log.**
- <YYYY-MM-DD> — <what landed; commit SHA; any follow-ups>
Status. done (2026-05-08).
Goal. Write docs/guides/m-doc-grammar.md defining the structured-tag extension to the existing ; doc: convention. Tags must be optional (existing prose-only blocks remain valid); the tag set is small (@param, @returns, @raises, @example, @since, @stable, @see, @deprecated, @internal); the first tag-free ; doc: line is the synopsis (consumed by m doc --short).
Approach.
- Borrow JSDoc verbatim where possible — the tag names above are all standard JSDoc.
- Show one fully-tagged worked example for
parse^STDJSONexactly as in plan § 3.1. - Define
@stablevalues:experimental/stable/deprecated. - Note the synopsis-line rule (godoc convention: first sentence is the summary).
- Flag the parsing contract for downstream tools (manifest generator + lint rule must accept the same grammar).
Acceptance. New docs/guides/m-doc-grammar.md exists; reviewed for clarity; the worked example matches what WA4's generator will be expected to extract.
Out of scope. Not changing the existing ; doc: prose convention; not specifying a Markdown subset for tag bodies (keep them plain text for v1).
Progress log.
- 2026-05-08 —
docs/guides/m-doc-grammar.mdlanded (10 sections, ~250 lines). Defines the nine tags, the synopsis line rule (godoc-style; inline comment on label line preferred, first prose sentence as fallback), the lexical model (continuation lines append; empty; doc:lines terminate a tag's body), and a workedparse^STDJSONexample showing both the tagged source form and the derived manifest entry. § 7 pins the parsing contract three downstream tools must agree on (tools/gen-manifest.m/WA4,M-DOC-001lint/WA3, all Wave B/C/D consumers via the manifest). § 8 tabulates edge-case behaviour (unknown tags, duplicate@param, etc.). Key calibrations vs the plan §3.1 sketch: (a) extrinsic-vs-procedure distinction is inferred statically fromquit <expression>rather than from formal-list parens; (b)@exampleblocks collapse consecutive lines into one snippet (more idiomatic for STDASSERT-style multi-line examples); (c)@internalexcludes a label from the manifest entirely rather than merely tagging it; (d) free-form description block can sit either before or after the tag block — recommended placement is tags-first, prose-second. No source files (src/STD*.m) touched — that is WA2's scope. WA3 (M-DOC-001lint rule) and WA4 (tools/gen-manifest.m) now have a normative spec to implement against and are unblocked.
Status. done (32/32 modules — all src/STD*.m backfilled as of 2026-05-08).
Goal. Every public label across all 30+ src/STD*.m routines gets @params, @returns, @raises, ≥1 @example, @since, @stable. The existing prose ; doc: lines remain as continuation.
Approach.
- One module per session; treat the v0.4.0 module-tracker order as a natural sequence.
- Cross-check
@paramnames against the formal-list; cross-check@raisesagainst the routine-header's documented$ECODEvalues. - For
@since, read fromCHANGELOG.md(per-module first-shipped tag). - For
@stable, default tostablefor any label that's been shipped in a tagged release;experimentalfor anything still in flight;deprecatedfor nothing yet. - Run
make checkper module before moving on.
Acceptance. Every public label tagged. Once WA4 ships, the manifest generator parses src/ without warnings.
Out of scope. Not changing label signatures or behaviour. Not deprecating anything in this pass — @stable=deprecated is reserved for a future intentional deprecation.
Progress log.
- 2026-05-08 — first 3 modules done (STDB64, STDHEX, STDFMT). Pattern validated end-to-end: grammar guide → tagged source → manifest extraction → manifest-driven frontmatter → errors registry. The 3 modules were chosen as the smallest/simplest of the 32 (5 + 4 + 2 public extrinsics) and as a representative cross-section: STDB64 and STDHEX are pure data-transform modules with no
$ECODEraises (so@raisesis empty); STDFMT is the first real test of@raisesplumbing because it sets four distinct,U-STDFMT-*,codes via araisehelper. Manifest globals: 519 → 502 public labels (the 13 internal helpers across these 3 modules —alpha,urlAlpha,encodeImpl,decodeImplin STDB64;alpha,alphaU,encodeImplin STDHEX;render,expand,lookup,apply,parseSpec,convert,toBase,pad,repeat,raisein STDFMT — all correctly excluded after@internal).dist/errors.jsonpopulated for the first time with the four STDFMT codes correctly tied tofandfn. Frontmatter regenerated with--forceto pick up the trimmedlabelslists and the populatederrors. Style/lint impact: zero.make fmt-checkclean (65/65 unchanged);make lint --error-on=errorreports 0 errors (8480 findings total, all S/W/I, pre-existing — none from these edits). Per-label tags applied:- STDB64:
encode/decode/urlencode/urldecode/valid→@param data|text(string, byte-string semantics noted),@returns string|bool,@example(carried over from the proseExample:lines),@since v0.0.2,@stable stable,@seecross-refs to siblings. No@raises(module never sets$ECODE). - STDHEX: same shape —
encode/encodeu/decode/validwith@since v0.0.2. The two encode entries point at each other via@see. - STDFMT:
f/fnget@paramfor every formal-list arg (9 + 2 respectively),@returns string, all four@raises U-STDFMT-*codes (since theraise()helper is reachable transitively from both extrinsics),@example,@since v0.0.3,@stable stable, mutual@see.
- STDB64:
- Style observations carried into future modules: (1) the existing inline-
Example:prose lines are removed from the description block once@examplecarries the same content (avoids duplication; matches the grammar guide's recommended style). (2) Internal helpers' "Internal — " prose prefix is dropped when@internalis added (the tag carries the same signal). (3) Tag-bodies are aligned at column position 24 within the; doc:body for readability; not enforced by the grammar but a nice convention. (4) For modules with 9-position positionals (STDFMT.f), all nine@paramlines are emitted even though the trailing ones may be optional in practice — explicit > implicit. - What's left for WA2: 29 of 32 modules. Recommended ordering (by simplicity): STDARGS, STDUUID, STDLOG, STDDATE, STDCSV, STDOS, STDSEMVER, STDSTR, STDTOML, STDCACHE, STDPROF, STDSNAP, STDENV, STDSEED, STDFIX, STDMOCK, STDFS, STDCSPRNG, STDCOLL, STDURL, STDMATH, STDXFRM, STDCRYPTO, STDCOMPRESS, STDHTTP, STDASSERT, STDREGEX, STDXML, STDJSON. The last 4 (STDASSERT, STDREGEX, STDXML, STDJSON) carry the most labels and most-complex error surfaces; do them last when the workflow is fully grooved.
- 2026-05-08 — WA2 complete (batches 5–11 land remaining 20 modules). Final 20 modules backfilled in five workflow-batches plus a bulk-transform pass for the largest three:
- Batch 5 (STDCACHE, STDPROF, STDSNAP) — STDSNAP gains
@raises U-STDFS-OPEN-FAILfor the transitive STDFS dependency. - Batch 6 (STDENV, STDSEED, STDFIX) — STDSEED's 6 distinct error codes correctly tied to load(), validate(), loadJson() per the transitive raise() chain. STDFIX raises U-STDFIX-EMPTY-TAG / U-STDFIX-UNREGISTERED-TAG.
- Batch 7 (STDMOCK, STDFS, STDCSPRNG) — STDFS captures the full byte-faithful I/O error matrix: NOT-WIRED + OPEN-FAIL + READ-TRUNCATED + REMOVE-FAIL across writeBytes/appendBytes/readBytes plus the v0.3.0 text-I/O paths.
- Batch 8 (STDCOLL, STDURL, STDMATH) — STDCOLL is the largest module backfilled (48 public labels: Set/Map/Stack/Queue/Deque/Heap/OrderedDict). All 13 STDURL internal helpers excluded via
@internal. - Batch 9 (STDXFRM, STDCRYPTO, STDCOMPRESS) — Phase 3 callout modules with rich
@raisessurfaces (CALLOUT-MISSING / DIGEST-FAIL / HMAC-FAIL / BAD-LEVEL / LIBZ-FAIL / LIBZSTD-FAIL). - Batch 10 (STDHTTP, STDASSERT) — STDASSERT's
silentcorrectly marked@internalafter the helper's own; doc:block already labelled itself "Internal helper". - Final batch (STDREGEX, STDXML, STDJSON) — these three carry ~140 internal helpers that needed
@internaltagging. Bulk transform via Python sed-like one-liner (re.sub(r'(\\s+); doc: Internal — (.)([^\n]*)', r'\\1; doc: @internal\\1; doc: \\u(\\2)\\3', text)) handled the prose-prefix cases in one shot; ~8 remaining internal helpers without "Internal —" phrasing got individual edits. Then 31 public labels backfilled with full tag set.
- Batch 5 (STDCACHE, STDPROF, STDSNAP) — STDSNAP gains
- Aggregate at WA2 close: 519 → 284 public labels in the manifest (235 internal helpers correctly excluded). errors.json: 0 → 43 distinct error codes across all 32 modules. Module-level error counts validate: STDARGS 5, STDSEED 6, STDFS 4, STDCRYPTO 3 + 3 (digest/hmac shared), STDCOMPRESS 4, STDFMT 4, STDDATE 3, STDLOG 3, STDCSPRNG 3, STDREGEX 3, STDFIX 2, STDJSON 2, STDCSV 1, STDENV 1, STDSNAP 1 (transitive U-STDFS-OPEN-FAIL).
- Style/lint impact:
fmt-checkclean (65/65 unchanged). Final lint: 0E 567W 7677S 272I (vs starting 0E 567W 7638S 275I). +39 S findings, -3 I — these are M-MOD-001 line-length warnings on a few@returns/@seelines that exceed the 100-byte limit. All S-severity, non-gating, comparable to the pre-existing 7638 such findings project-wide. Zero new errors introduced. - Frontmatter regenerated (
make frontmatter --force) so all 32docs/modules/std*.mdfiles have up-to-datelabelsanderrorslists. - Wave A status now ALL DONE in this repo (WA1 + WA2 + WA4 + WA5 + WA6 + WA7 + WA8 = 8/8). Cross-repo follow-ons remain: WA3 (m-cli
M-DOC-001lint rule), Wave B (m-clim docfamily), Wave C (VS Code extension), Wave D (AI skill). - 2026-05-08 — fourth batch (STDSEMVER, STDSTR, STDTOML). Three "predicate-style" P4-wave modules — no
$ECODEraises anywhere; failures surface as""or0returns instead. Public-label deltas: STDSEMVER 18→9 (10 internal: validTriple/validPrerelease/validBuild/validNumericId/validPreId/validBuildId/comparePrerelease/comparePreId/matchesOne/matchesCaret); STDSTR 13→13 (no internal helpers — STDSTR is a flat utility module); STDTOML 13→4 (9 internal: parseTable/parsePair/decodeValue/decodeString/decodeInteger/decodeFloat/trimWs/stripTrailingComment/validBareKey). Aggregate: 466→449 public labels.dist/errors.jsonunchanged at 16 codes — these three modules don't raise. Style observation: STDSTR is the first all-public module backfilled; it has no@internaltags at all. Net lint delta: +1S, -1I, essentially zero. fmt-check clean (65/65 unchanged). - 2026-05-08 — third batch (STDLOG, STDCSV, STDOS). Same workflow. Public-label deltas: STDLOG 17→8 (9 internal: collect/emitLine/emitJson/writeLine/kvVal/needsQuote/escape/replace/levelNum); STDCSV 6→4 (2 internal: emit/dq); STDOS 12→11 (1 internal: replaceDouble). Aggregate: 478→466 public labels.
dist/errors.jsonnow carries 16 codes (was 12): +3 STDLOG (INVALID-LEVEL→ LEVEL;INVALID-SINK→ SINK;INVALID-FORMAT→ FORMAT — each tied to its single direct setter, since these don't go through araise()thunk) plus +1 STDCSV (OPEN-FAIL→ parseFile + writeFile). Net lint delta: ZERO —0E 567W 7652S 274Iunchanged from the prior batch. The pattern is now grooved: tag bodies stay under the 100-byte M-MOD-001 limit, prose continuation handles the longer descriptions. Style observation carried forward — STDLOG's 5 level-emitter stubs (DEBUG/INFO/WARN/ERROR/FATAL) all share the same 11-formal positional pattern, so DEBUG carries the full@paramlist and the other four emitter docs reference it via; doc: @param k1 string key 1 (optional; same shape through k5/v5)to avoid 50 lines of redundant tag boilerplate. The manifest still records the formal-list correctly (extracted from the label-line itself, not the@paramcount); futurem docUX can render the cross-ref or expand it as desired. - 2026-05-08 — second batch (STDUUID, STDARGS, STDDATE). Same workflow as the first batch. Public-label deltas: STDUUID 8→5 (
randomHex/toHex/unixMsexcluded); STDARGS 15→7 (8 internal helpers excluded —initDefaults/checkPositionals/walk/tokenize/handleLong/handleShort/assignPositional/raise); STDDATE 20→7 (13 internal helpers excluded — the Howard-Hinnant calendar primitives, the strftime/strptime tz formatters, andpadL). Aggregate: 502→478 public labels across the manifest.dist/errors.jsonnow carries 12 codes (was 4): the 4 STDFMT codes from batch 1, plus all 5 STDARGS codes (UNKNOWN-ACTION→addflag;UNKNOWN-FLAG/UNKNOWN-SUBCOMMAND/MISSING-VALUE/MISSING-POSITIONAL→parse), plus all 3 STDDATE codes (BAD-HOROLOG→fromh/strftime/add;BAD-ISO→toh/strptime;BAD-DUR→add). The transitive@raisesdiscipline validated:parse^STDARGScorrectly captures all four codes that the helper chain (walk/handleLong/handleShort/checkPositionals) raises through the sharedraise()thunk, even thoughparseitself only callswalkdirectly. Style notes carried forward: a few new@returnsand@seelines exceed the 100-byte M-MOD-001 limit — kept as-is because (a) the warnings are S-severity, not gating; (b) splitting them across continuation lines reduces readability; (c) the trend is project-wide (7652 such findings pre-existing). Net lint delta this batch: 0E unchanged; +14S (style); -1I (info).make fmt-checkclean (65/65 unchanged).
Status. done (2026-05-08; m-cli commit f97b849 pushed to origin).
Goal. Warn on public labels missing required tags. Lives in m-cli (where lint rules live), not m-stdlib.
Approach.
- Locate the existing m-cli lint rule directory; follow the pattern of an existing M-* rule.
- Severity = warn (not error) at first; promote to error in a later release once WA2 backfill is complete and stays clean.
- Required tags by label form: extrinsic (
label() ; ...) needs@returns; procedure (label(args) ; ...) needs@parammatching every formal arg; both need ≥1@exampleif@stable=stable.
Acceptance. m lint against m-stdlib emits zero M-DOC-001 errors after WA2; emits warnings only on labels not yet backfilled.
Out of scope. Stricter rules (M-DOC-002+) — defer to a later pass. The acceptance-gate threshold change to --error-on=warning for M-DOC-* is also out of scope until backfill is complete.
Progress log.
- 2026-05-08 (m-cli commit
f97b849) — Landed as the first member of the M-DOC-NN family. New filesrc/m_cli/lint/_doc.py(~290 LoC) implements the rule following the same_label_body_extents+ tag-block parser pattern thattools/gen-manifest.pyin m-stdlib uses, so the lint rule and the manifest generator agree byte-for-byte on which; doc:blocks belong to which label. New tests filetests/test_lint_doc.py(17 cases) covers each diagnostic shape (missing @param / extra @param / missing @returns / undeclared @raises / missing @since / missing @stable / unknown @stable level /@stable deprecatedwithout@deprecated) plus the rule's profile/tag membership (modern + doc tags). New side-effect import inlint/__init__.pyregisters the family. - Severity =
Severity.WARNINGper the grammar guide §7.2 v1 spec. Tags =("modern", "doc")so the rule lands in the default, modern, and pythonic profiles automatically;--rules=docselects just the M-DOC family explicitly. Category =Category.DOCUMENTATION. - Two implementation calibrations during the work: (1)
@raisesdetection scans only code lines (skips; doc:lines) — caught a false positive while validating against m-stdlib'sSTDCSPRNG.available(), where the@examplebody itself contains aset $ecode=,U-MYAPP-NO-CSPRNG,to demonstrate caller code; that's documentation, not a code-side raise. Pinned by a regression test. (2) Transitive @raises (codes raised by helpers the label calls but doesn't catch) is intentionally NOT inferred — would need a workspace call graph the m-cli lint engine doesn't yet have (cross-routine pass is the same gap M-XINDX-007/008 face). v1 surfaces direct raises only. The grammar guide §5.3 acknowledges this as an under-reporting trade-off; the manifest'sraised_in_bodyinformational field captures the same direct-raise set for consumers wanting it. - Validation against m-stdlib post-WA2:
m lint --rules=M-DOC-001 src/reports zero findings (every public label in src/ carries the required tag set). Project-widemake lintjumps from 567W → 600W (+33), all M-DOC-001 findings againsttests/STD*TST.mfiles — test routines are NOT in WA2's scope, but M-DOC-001 correctly flags them as candidates for a follow-on backfill pass. 0E unchanged (M-DOC-001 is WARNING, doesn't gate--error-on=error). - STDLOG side-fix (m-stdlib commit pending alongside this row's update): the four level-emitter labels INFO/WARN/ERROR/FATAL each have 11 formals (
event,k1,v1,...,k5,v5) but I'd taken a shortcut during WA2 and only annotated@param k1(with note "same shape through k5/v5"). M-DOC-001 correctly flagged the 36 missing@paramtags. Backfilled to add the 9 missing per-label@paramtags each — DEBUG already had the full set; INFO/WARN/ERROR/FATAL now match. After the fix: 0 M-DOC-001 findings against src/. Manifest regenerated. - Acceptance met. Closing WA3.
Status. done (2026-05-08).
Goal. tools/gen-manifest.m walks src/STD*.m and writes dist/stdlib-manifest.json per the schema in plan § 3.2.
Approach.
- Hand-rolled M parser (~150 LoC). Decision recorded in plan § 11.4 / tracker D3 — do NOT bind to tree-sitter-m for v1.
- Recognised tokens: routine line (column 1, all-caps name); label line (column 1, lowercase or camel name, optional formal-list in parens); comment line (
;);; doc:block (special). - Emit JSON via
$$encode^STDJSON— eat our own dog food. - Provide a Makefile target
make manifestthat runs the generator. - Source location: each label entry carries
source: {file, line}so VS Code goto-def works without a separate index.
Acceptance. make manifest produces dist/stdlib-manifest.json containing every public label with the full schema. Manual spot-check against STDJSON, STDDATE, STDREGEX confirms accuracy.
Out of scope. Markdown rendering; HTML generation; per-module slim manifests (deferred per plan § 3.2 "until a consumer asks").
Progress log.
- 2026-05-08 — Generator landed as
tools/gen-manifest.py(~370 LoC of Python), pivoting away from the planned.mimplementation. Reason for the pivot (open redirect point if user prefers M): (a) there is no clean home for a non-stdlib M routine —src/STDMAN.mwould pollute the consumer-facing stdlib promise, andtools/*.mhas no precedent or routine-path wiring in m-cli'sm testdiscovery; (b) Python'sjsonstdlib emits canonical output cheaply where bash + jq would have been awkward (jq is not installed on host); (c) Python is already a project dependency through m-cli; (d) the plan § 3.2 explicitly allows alternative implementations ("or.shwrapping..."). The eat-own-dog-food argument for using STDJSON loses out to shipping speed for v1; can be revisited if a real consumer needs it. Also pivoted artefact path:tools/gen-manifest.m→tools/gen-manifest.py(tracker row updated). What landed:tools/gen-manifest.py— module-file walker (parse_module_file), label-line parser (parse_label_line), doc-block tag extractor (parse_doc_block), label entry builder (build_label_entry), manifest assembler (build_manifest), and an inline--self-testmode that parses a syntheticSTDFOOfixture and asserts structural correctness (passes; runs in <100ms). ReadsCHANGELOG.mdto populatestdlib_version(currentlyv0.4.0). Emitsdist/stdlib-manifest.jsonanddist/errors.json(the inverted error-code index — empty until WA2 adds@raisestags; this also covers WA7's deliverable).- Schema as implemented:
{stdlib_version, generated_at, modules: {NAME: {synopsis, description, errors, labels: {NAME: {form, signature, synopsis, params, returns, raises, raised_in_body, examples, since, stable, see_also, deprecated, description, source: {file, line}}}, source: {file, line}}}, errors: {CODE: {module, labels}}}. Theraised_in_bodyfield is informational (lists,U-STDxxx-,codes detected viaset $ecode=static scan) — it powers the WA3 lint rule's cross-check between actual raise sites and declared@raises. Calibration vs the plan: addedraised_in_body(not in plan § 3.2 schema) for the WA3 hand-off; added module-leveldescription(the routine-header prose under the synopsis) since it's free signal. Module-leveltag/phase/stable/storagefrom plan § 3.2 are not yet populated — those come fromdocs/modules/*.mdfrontmatter once WA6 lands; the generator can merge them in then. - Doc-comment grammar parser: implements docs/guides/m-doc-grammar.md § 3 strictly. The key call: indent-based continuation — a non-tag line whose body starts with whitespace extends the prior tag; a non-indented non-tag line flushes the current tag and joins the description block. This made the
parse^STDJSONworked example in §6 of the grammar guide work as written, without requiring an empty; doc:separator between the tag block and the post-tag prose. - Makefile: new
manifest(regenerate) andmanifest-check(regenerate +git diff --exit-code) targets. Themanifest-checktarget is the local equivalent of WA5; the actual CI workflow change is still WA5's scope.
- Real-world output.
make manifestagainst current src/ (no WA2 backfill) reports 32 modules, 519 public labels indist/stdlib-manifest.json(415 KB, 10,326 lines). Spot-checks confirm: (1) STDJSON.parse correctly classified extrinsic withsignature: "$$parse^STDJSON(text, root)"andsource: {file: "src/STDJSON.m", line: 39}; synopsis + description extracted faithfully. (2) Internal labels with no; doc:block (parseFail,encodeFail,parseFileEof) are correctly excluded. (3) Labels that have architecture-explainer; doc:blocks but aren't part of the public surface (e.g.parseArray,encodeValue, STDASSERT'srecordFail) currently leak into the manifest — these need@internaltags during WA2 backfill, by design (the grammar's design call: better to ship leak-on-the-side-of-overinclusion than hide labels with documented contracts). (4)dist/errors.jsonis{}because no@raisestags exist yet — this is correct; backfill repopulates. - Two known sub-issues, deferred: (a) The
signaturefield reflects the formal-list verbatim (text, root), losing the by-reference convention (text, .rootat call sites) — needs either a@param-level by-ref marker (grammar refinement) or static analysis (e.g.kill <name>/merge <name>=inside the body implies by-ref). Not v1 critical. (b) Module-levelphase/tag/stable/storagefields from plan § 3.2 schema are absent at module level — they originate indocs/modules/*.mdfrontmatter (WA6) and will merge in once that lands. - Unblocks. WA5 (CI gate — wire
make manifest-checkinto.github/workflows/ci.yml); WA7 already covered (errors.json ships in the same generator); all of Wave B (m docfamily readsdist/stdlib-manifest.json); WC1 (VS Code extension reads the same manifest); WD1 (AI skill regenerates from manifest).
Status. done (2026-05-08).
Goal. CI fails when dist/stdlib-manifest.json drifts from what tools/gen-manifest.m would produce.
Approach.
- Add a CI step:
make manifest && git diff --exit-code dist/stdlib-manifest.json. - Mirrors the existing
m fmt --checkpattern. - The committed manifest is the authoritative artefact; any commit that touches a
; doc:block must include the regenerated manifest in the same commit.
Acceptance. CI green when manifest matches; CI red when a tag is added/removed in src/ without re-running make manifest.
Out of scope. Auto-regenerating in CI and committing back — keep the regeneration as a developer responsibility (matches the m fmt model).
Progress log.
- 2026-05-08 — CI gate landed in
.github/workflows/ci.ymlas a new "Manifest drift check" step on the m-stdlib job, placed after the m-cli/tree-sitter-m install but beforeInitialise YDB workspace. The step runsmake manifest-check(the local target landed with WA4) which regenerates the manifest and runsgit diff --exit-code dist/stdlib-manifest.json dist/errors.json. Engine-free — only needspython3+git, both already installed by the prior steps. Not added to the iris-portability-check job because the manifest is engine-agnostic (one drift gate is sufficient). Not added to the localmake checktarget — TDD loops touch; doc:blocks frequently and would trip the gate during normal iteration; CI is the authoritative gate,make manifest-checkis the on-demand local equivalent. - Pre-flight fixes to
tools/gen-manifest.pyto make the gate viable. The first manifest landed in WA4 had agenerated_at: <wall-clock>field that changed every run, which would have caused the gate to fail spuriously on every commit. Removed the field outright — the information it carried (when was the manifest produced) is already covered bystdlib_version(sourced from CHANGELOG) andgit log dist/stdlib-manifest.json(commit history). Also droppedimport datetimeand the_dt.UTCreference (Python 3.11+ only) for cross-version portability. Verified determinism with two consecutivemake manifestruns — byte-identical output. - Unblocks. Wave A close: WA6 (frontmatter on docs/modules/*.md, no deps) + WA8 (release tag) are the remaining stdlib-side rows. WA7 (errors.json) was already covered by WA4's generator. WA2 (backfill) and WA3 (lint rule in m-cli) remain.
Status. done (2026-05-08).
Goal. Every docs/modules/stdXXX.md gains YAML frontmatter (per plan § 3.3 schema).
Approach.
- Hand-edit each file (only ~20 modules).
- Treat the manifest as authoritative when frontmatter and manifest disagree (consumers read manifest first).
- Optional: small check script that warns on drift (
tools/check-doc-frontmatter.m); not a CI gate yet.
Acceptance. Every per-module markdown parses as YAML+Markdown and carries the documented field set.
Out of scope. Generating these markdowns from the manifest — deferred. The markdown is human-written prose; the frontmatter is just structured metadata on top.
Progress log.
- 2026-05-08 — Pivoted from "hand-edit each file" to a small idempotent backfill tool,
tools/write-module-frontmatter.py(~180 LoC). Readsdist/stdlib-manifest.json(synopsis, labels, errors per module) plusdocs/modules/index.md(phase + tag tables, conformance corpora table, cross-module dependency bullets) and prepends a YAML frontmatter block to eachdocs/modules/std*.md. Idempotent on re-run — files that already start with---\nare skipped;--forceregenerates. New Makefile targetmake frontmatterinvokes it. - What landed: all 32 module docs now carry frontmatter — module / tag / phase / stable / since / synopsis / labels / errors / conformance / see_also. Spot-check on
docs/modules/stdjson.mdshows the right shape (phase: Phase 2, tag: v0.2.0, conformance: ['tests/conformance/json/'], see_also: ['STDLOG', 'STDSEED'] — derived from index.md cross-deps). - Calibration vs the plan §3.3 schema. The plan listed
storageas a field; dropped it from the auto-derived set because it pointers at an in-doc anchor and changes per-module (better expressed in the prose itself). All other plan fields are populated. Stable defaults to "stable" for every module currently in a tagged release; experimental/deprecated will need hand-edits when those states arise. - Known limitations the user can iterate on:
labelsincludes internal helpers (e.g. STDJSON'sparseArray,encodeValue) because the manifest currently lists every label with a; doc:block. Once WA2 backfills@internaltags and the manifest regenerates,make frontmatter --forcecleans up the labels list.errorsis[]everywhere because no@raisestags exist yet — also resolves with WA2.synopsisis the verbatim first line of the routine header withm-stdlib —and trailing period stripped. Some have parenthetical track/tag noise (e.g. STDHTTP: "HTTP/1.1 client (track H3, target tag v0.4.0)"). Cleaning these is a separate src/ pass — not WA6's scope.see_alsois derived from index.md's cross-deps section — modules without a runtime dep relationship get[]. Hand-edits welcome; the tool's--forcewould clobber them today, so a future iteration should diff hand-curated fields and merge rather than overwrite.
- Unblocks WA8 (release tag) — the only remaining stdlib-side row. WA2 (backfill) and WA3 (m-cli lint) are cross-repo.
Status. done (2026-05-08; structurally rolled into WA4).
Goal. dist/errors.json is the inverted index: U-STD* code → producing module + labels.
Approach.
- Extend
tools/gen-manifest.mto emit a second file. Same source data, different shape. - Cheap derivative — adds a few lines to the generator.
Acceptance. make manifest writes both dist/stdlib-manifest.json and dist/errors.json; the errors index covers every ,U-STDxxx-NAME, referenced in any @raises tag.
Out of scope. Per-error long-form descriptions — those live in the routine headers and per-module docs.
Progress log.
- 2026-05-08 — Done as part of WA4.
tools/gen-manifest.pywalks every label's@raisestags and aggregates an inverted{code: {module, labels: [...]}}index, written todist/errors.jsonalongside the manifest. The CI manifest-check gate (WA5) covers this file too.dist/errors.jsonis currently{}because no@raisestags exist in src/ — once WA2 backfill adds them, the file populates without further generator work.
Status. done (2026-05-08; tagged v0.5.0 locally, not pushed).
Goal. Tag a release that ships the manifest, errors registry, frontmatter, and doc-grammar guide as one user-facing event. Update docs/modules/index.md with a "Machine-readable surface" subsection linking both dist/ artefacts.
Approach.
- CHANGELOG entry under a new tag (decide
v0.5.0vsv0.4.1based on whether Phase 4 module work has also accumulated — coordinate with the livemodule-tracker.md). - Add the "Machine-readable surface" subsection to
docs/modules/index.mdlinking the manifest, errors.json, and the new grammar guide. - Ensure
docs/modules/index.mdand the manifest agree on the module list.
Acceptance. The new tag exists; CHANGELOG documents Wave A; docs/modules/index.md links the new artefacts.
Out of scope. Wave B/C/D consumer rollout — those waves can ship under their own tags later.
Progress log.
- 2026-05-08 (prep) — All non-tag-cut prep work landed:
CHANGELOG.mdgains an[Unreleased]section that captures Wave A — M-doc tag grammar v1, manifest generator + dist/, CI manifest-drift gate, frontmatter on all 32 module docs, the index.md machine-readable surface section. Frames the release as doc + tooling only (nosrc/STD*.mruntime behaviour change). Calls out WA2 + WA3 as the high-leverage Wave-A follow-ons.docs/modules/index.mdgains a "Machine-readable surface" subsection (right under the lead-in paragraph, before "Phase 1") — table of artefacts (dist/stdlib-manifest.json,dist/errors.json,docs/guides/m-doc-grammar.md, per-module frontmatter) plus regeneration commands (make manifest,make frontmatter,make manifest-check).- Pre-flight fix to
tools/gen-manifest.py:read_stdlib_version()now skips an[Unreleased]heading and returns the most-recent versioned entry, so the manifest'sstdlib_versionstays anchored to the last shipped tag (v0.4.0today) until WA8's tag is cut. Without this fix, adding[Unreleased]to CHANGELOG would have flippedstdlib_versionto empty string and the WA5 gate would have fired spuriously.
- Pending — user decision points:
- Version number.
v0.5.0(SemVer minor-bump for new tooling surface) vsv0.4.1(treat the docs/tooling delta as a patch). Recommendation:v0.5.0— the manifest is a new public API consumers will depend on, even if nosrc/runtime changes. - Timing. Tag now (Wave A is shippable as-is — manifest + frontmatter + grammar + CI gate all green), or wait for WA2 backfill to land first so the manifest's tag-derived fields populate before the version cut. Recommendation: tag now (decoupled releases — Wave A tag ships the surface, a follow-on tag ships the populated manifest from WA2).
- Coordination with module-level work. If a Phase 4 module is mid-flight (Table 2 candidates: STDYAML / STDMATH / STDXFRM / STDNET), bundle vs. solo-cut. The current
module-tracker.mdsays all numbered T-tickets T1-T30 are closed and Table 2 has only proposed (not started) modules — solo-cut is clean today.
- Version number.
- Action when user gives the go-ahead:
- Edit
[Unreleased]heading in CHANGELOG.md →[v0.5.0] — 2026-MM-DD(or chosen version + date). make manifestto regeneratestdlib_versionto the new tag.- Commit the CHANGELOG header + manifest update as the release-sync commit (precedent:
53ecf70for v0.4.0). git tag -a v0.5.0 -m "<release annotation>". Decide whether to push (git push origin v0.5.0) per project policy.- Flip this row to
done.
- Edit
- 2026-05-08 (release sync) — User accepted both recommendations. Cut v0.5.0: CHANGELOG
[Unreleased]heading flipped to[v0.5.0] — 2026-05-08;make manifestre-ran andstdlib_versionflipped tov0.5.0in bothdist/stdlib-manifest.jsonanddist/errors.json. Tagv0.5.0cut locally with annotated message enumerating Wave A scope. Tag not pushed — branch is local-only per session policy. Frontmatter not regenerated becausesince:per-module is anchored to each module's first-shipped tag (e.g. STDJSON's staysv0.2.0), not the rolling stdlib release. Wave A m-stdlib-side: ALL DONE (WA1+WA4+WA5+WA6+WA7+WA8). Cross-repo: WA2 (src/ tag backfill), WA3 (m-cli M-DOC-001 lint), WB1 (m-clim doc).
Status. done (2026-05-08; m-cli commit 0024a72 pushed to origin).
Goal. The flagship lookup command, modelled on go doc. Plan § 4.1 has the full UX spec.
Approach.
- New file in m-cli's command dir (verify path: likely
~/projects/m-cli/src/cmd/doc.m; mirror existing command file conventions). - Resolve m-stdlib via the same path the existing m-cli commands use; load
dist/stdlib-manifest.json. - Three forms:
m doc STDJSON(module),m doc STDJSON.parse(label),m doc parse(fuzzy across modules). - Output formatter: synopsis on top, signature highlighted, params / returns / raises blocks, example, since / stable / see line, source pointer at the bottom.
Acceptance. All three forms return correct output ≤ 100ms cold for the v0.5.0 manifest size.
Out of scope. Pager support, colour, network lookups (alt manifest sources). Keep v1 lean.
Progress log.
- 2026-05-08 (m-cli commit
0024a72) — Repurposed the existingm docCLI from path-based extract-to-Markdown to godoc-style symbol lookup overdist/stdlib-manifest.json. Three-file module (src/m_cli/doc/):lookup.pyowns manifest discovery (4-level fallback: --manifest flag → $M_CLI_MANIFEST env → walk-up from cwd →~/projects/m-stdlib/dist/stdlib-manifest.json) and symbol resolution (classifies into module / module.label / fuzzy bare-name);format.pyowns three output forms (long with signature/params/returns/raises/example/source; short = one-line synopsis; json = raw manifest entry);cli.pyis the rewritten argparse handler that ties them together. Legacyextract.py/render.pykept on disk as a programmatic surface — only the CLI was repurposed. The 4 olddoc_commandtests intest_doc.pywere dropped since they exercised the path-based CLI behaviour that no longer exists; the 12 library-direct tests for extract/render still pass. WB2 is structurally absorbed into WB1 because--shortand--jsonare wired in this commit too (the plan separated them as a follow-on task; landing them together cost one extra flag definition and three test classes — total cost was negligible vs. shipping them separate). WB3 fuzzy search is also partially live here:m doc <bare-name>does cross-module exact-name lookup with multi-hit listing — the broaderm search "phrase"substring scan is still WB3. - 30 new tests in
tests/test_cli_doc_lookup.pycovering: lookup classification (module / dotted / bare / unknown), manifest discovery order (explicit/env/walk-up/missing), all three output forms (module long/short/json + label long/short/json + multi-hit list), and CLI error paths (missing manifest → exit 2, malformed JSON → exit 2, no match → exit 1, found → exit 0). All pass. - End-to-end against m-stdlib v0.5.0:
m doc STDJSON.parserenders the full godoc-style block with signature$$parse^STDJSON(text, root) → bool, params/returns/raises tables, since/stable/see line, the@examplebody, andsource: src/STDJSON.m:39.m doc parsefinds 8 cross-module hits (STDARGS/STDCSV/STDENV/STDJSON/STDSEMVER/STDTOML/STDURL/STDXML each have aparselabel).m doc(no args) lists all 32 modules with synopses. Cold latency ~30ms (well under the 100ms target). - Pre-existing manifest generator bug surfaced and fixed in m-stdlib (committed alongside this row's update):
tools/gen-manifest.py'sROUTINE_LINE_REregex capped routine-name length at 8 chars (the M89 standard limit), but YDB and IRIS allow up to 31. STDASSERT (9), STDCRYPTO (9), STDCSPRNG (9), STDSEMVER (9), STDCOMPRESS (11) were all returning emptysynopsisin the manifest. Relaxed to[A-Z][A-Z0-9]{0,30}. Manifest regenerated; all 32 modules now have non-empty synopses; frontmatter on the affecteddocs/modules/*.mdregenerated to match. - Unblocks: WB2 (
--json+--shortalready shipped), WB3 (m search— keyword-search variant of WB1), WB4 (m manifest/m examples/m errorsthin wrappers — all read the same manifest), WC1 (VS Code extension can consume the same manifest via the same path-resolution logic via a TypeScript port offind_manifest), WD1 (AI skill regenerator already has its input format pinned).
Status. done (2026-05-08; landed in the same m-cli commit 0024a72 as WB1).
Goal. Scripting hooks. --json emits the raw manifest entry; --short emits one-line synopsis (godoc -short equivalent).
Approach. Trivial flag-handling on top of WB1.
Acceptance. m doc --json STDJSON.parse | jq .signature returns the signature string.
Out of scope. YAML / TOML output formats. JSON is enough.
Progress log.
- 2026-05-08 — Shipped together with WB1 in m-cli
0024a72. Verified end-to-end:m doc --json STDFMT.f | python3 -c 'import sys,json; print(json.load(sys.stdin)["signature"])'→$$f^STDFMT(template, a1, a2, a3, a4, a5, a6, a7, a8, a9).m doc --short STDASSERT.eq→STDASSERT.eq — Assert actual=expected (string equality).. Both forms covered by tests intests/test_cli_doc_lookup.py(TestDocCommandShort, TestDocCommandJson).
Status. done (2026-05-08; m-cli commit 6542e73 pushed to origin).
Goal. Fuzzy search across manifest synopsis + description + examples.
Approach.
- Linear scan of the manifest (cheap at this size; revisit if it slows down).
- Substring match, case-insensitive. Rank hits in synopsis above hits in examples.
Acceptance. m search "json parse" lists relevant labels with their synopses. m search "URL encode" finds STDURL.encode.
Out of scope. True fuzzy ranking (BM25, trigram). Substring is enough for v1.
Progress log.
- 2026-05-08 (m-cli
6542e73) — Landed at~/projects/m-cli/src/m_cli/doc/search.py(~115 LoC). AND-style match across query tokens (every space-separated token must appear somewhere in the label's haystack — synopsis ∪ description ∪ examples). Three-tier ranking: tier 0 = primary token in synopsis (highest priority), tier 1 = primary token in description, tier 2 = primary token only in examples. Within tier, results sort by (module, label) for determinism.--limit Ntruncates output (default 50); a footer message reportsshowing N of M matcheswhen truncation kicks in. Manifest discovery is shared withm docvia the existingfind_manifest()fromlookup.py— same fallback chain (--manifest flag → $M_CLI_MANIFEST → walk-up from cwd →~/projects/m-stdlib/dist/...). Exit codes: 0 = matches found, 1 = no matches, 2 = manifest unreachable. End-to-end against m-stdlib v0.5.0:m search "URL encode"correctly returns STDB64.urldecode + STDB64.urlencode + STDURL.encode (the first two via @example bodies that contain "URL"; STDURL.encode via synopsis).m search "json parse"returns 6 hits ranked by tier (STDJSON.valid + STDLOG.FORMAT in tier 0 because both labels' synopses have "json" or "JSON"; STDJSON.parse + STDJSON.parseFile + STDJSON.lastError + STDJSON.valueOf in tier 1/2). 7 unit tests intests/test_cli_manifest_subcommands.py::TestSearchcovering case-insensitivity, AND-match, ranking, --limit truncation, no-match, missing-query.
Status. done (2026-05-08; m-cli commit 6542e73 pushed to origin — landed in same commit as WB3).
Goal. Thin wrappers that surface the manifest's substructures directly. Plan § 4.3–4.5.
Approach. Three short commands — each ≤ 30 LoC. They wrap reading the manifest / errors.json and emitting the relevant subsection.
Acceptance. Each command does its one job and pipes cleanly into jq or text tools.
Out of scope. Filtering flags beyond what's already there in m doc.
Progress log.
- 2026-05-08 (m-cli
6542e73) — Three sibling files under~/projects/m-cli/src/m_cli/doc/, each its own top-level CLI command, all sharing the manifest discovery fromlookup.py:m manifest [path](manifest.py~80 LoC) — emits the resolved manifest as JSON. With no path → whole manifest. WithSTDJSON/STDJSON.parse/modules/errors/stdlib_version→ just that subtree. Pipes cleanly intojq. Verified:m manifest stdlib_version→"v0.5.0";m manifest STDJSON.encode | jq .signature→$$encode^STDJSON(node).m examples [MODULE](examples.py~70 LoC) — walks every label's@examplebodies and emitsmodule.label: <body>lines for grep-friendliness. With aMODULEarg, scopes the walk to that module. Multi-line example bodies emit one prefixed line per source line. Verified:m examples STDFMT→STDFMT.f: write $$f^STDFMT(...),STDFMT.fn: set a("n")=....m errors(errors.py~95 LoC) — inverted index over@raisestags. Readsdist/errors.json(m-stdlib's WA7 sidecar) when present and falls back to deriving from the main manifest's per-labelraisesarrays — both produce identical output.--jsonemits the same shape as the sidecar for scripting. Verified:m errorslists all 43 codes from m-stdlib v0.5.0, each with producing module + every label that raises it (e.g.U-STDCOMPRESS-CALLOUT-MISSING STDCOMPRESS: gzip, gunzip, deflate, inflate, zstdCompress, zstdDecompress).
- 17 unit tests in
tests/test_cli_manifest_subcommands.pycovering: m manifest (full output, MODULE / MODULE.label / top-level subpaths, unknown paths); m examples (all-modules walk, module filter, no-examples module, unknown module); m errors (derivation from manifest, sidecar preference, --json flag, empty manifest). Plus 2 shared tests covering missing-manifest → 2 and malformed-manifest → 2 across all four WB3+WB4 commands. - Wave B status: ALL DONE — WB1+WB2+WB3+WB4 = 4/4 rows. The full manifest-reader CLI surface from plan §4 is shipped:
m doc <symbol>(long/short/json),m search <query>,m manifest [path],m examples [module],m errors. Cold latency ~30ms per command; manifest is a single ~415KB JSON read on each invocation.
Status. done (2026-05-08; new repo rafael5/m-stdlib-vscode initial commit 1c15a05 pushed).
Goal. Manifest-driven hover, goto-definition, and completion in VS Code for .m files. Plan § 5.1 has the capability list.
Approach.
- First decision: new repo
~/projects/m-stdlib-vscode/vs subdir~/projects/m-cli/vscode/. Recommend new repo — keeps m-cli's release cycle clean and the VS Code extension publishable independently. - TypeScript extension. On activation, locate the workspace's resolved m-stdlib (look for
.m-cli.tomlupwards; resolve install path the same waym docdoes; loaddist/stdlib-manifest.json). - Register hover provider on
^STD*andparse^STDJSON-style tokens. - Register definition provider using
source.{file,line}from the manifest. - Register completion provider scoped to
.mfiles. - Hard scope: this is not a full M LSP. Stdlib symbols only.
Acceptance. Hover on parse^STDJSON shows synopsis + signature. Cmd/Ctrl-click jumps to src/STDJSON.m:39. Typing ^STD shows a completion list of every module.
Out of scope. Full M LSP, project-aware symbols outside m-stdlib, lint integration, format-on-save (m-cli already has those via CLI).
Progress log.
- 2026-05-08 (m-stdlib-vscode commit
1c15a05) — Repo confirmed as~/projects/m-stdlib-vscode/per user direction. Initial v0.1.0 release lands the three providers wired todist/stdlib-manifest.jsonfrom m-stdlib's WA4 generator.src/manifest.ts(~145 LoC) — 4-level discovery resolver mirroring m-cli'sfind_manifest():m-stdlib.manifestPathsetting →$M_CLI_MANIFESTenv var → walk up from each workspace folder →~/projects/m-stdlib/dist/stdlib-manifest.jsonfallback. mtime-keyed cache (loadManifest()re-reads when the file's mtime changes). Tilde-expansion for~/...paths. Full TypeScript types covering the manifest schema.src/tokens.ts(~155 LoC) — text-based token classifier (tokenAt,modulePrefixAt,labelPrefixAt). Recognises^STDJSON,parse^STDJSON,$$parse^STDJSON(...)at any cursor position inside the matched span. The classifier is intentionally text-based — the extension doesn't spawn tree-sitter-m. Per the plan §5.1 hard-scope, the extension is stdlib-only and that scope is unchanged.src/hover.ts(~165 LoC) —StdlibHoverProvider. Render mirrors m-cli'sformat_label_long/format_module_long(signature in code-fence; params/returns/raises bullets; since/stable; see-also; example; description; source pointer) but asvscode.MarkdownString. Module hovers list every public label with synopsis. Honoursm-stdlib.enableHover.src/definition.ts(~85 LoC) —StdlibDefinitionProvider. Resolvessource.file(manifest field, repo-relative) against the manifest's repo root (<distDir>/..) and jumps tosource.line(1-based → 0-based VS Code Position). Falls back to opening the routine file at line 1 for module references.src/completion.ts(~115 LoC) —StdlibCompletionProvider. Two trigger shapes:^STD<prefix>lists every module (Modulekind + synopsis as detail);<prefix>^STDJSON(cursor inside the label-prefix region) lists every public label of STDJSON whose name starts with<prefix>(Functionkind). Registered with^as a completion-trigger char so VS Code surfaces module suggestions the moment the user types it.src/extension.ts(~95 LoC) — activation ononLanguage:m. Registers all three providers, installs aFileSystemWatcherper workspace folder for the resolved manifest path (regenerations frommake manifestflow into the editor without reload), surfaces a one-shot status-bar warning when no manifest is reachable.
- 22 unit tests pass (
tests/tokens.test.ts+tests/manifest.test.ts, both running undernode --test --experimental-strip-typesfor direct TS execution without a separate build step). Coverage: token classification across all three forms, completion-prefix detection, manifest discovery order, mtime-keyed cache invalidation, tilde-expansion. tsc clean (tsc -p ./zero errors withstrict: true). - Settings exposed:
m-stdlib.manifestPath(override discovery),m-stdlib.enableHover/m-stdlib.enableDefinition/m-stdlib.enableCompletion(per-provider toggles, default on). - Acceptance verified (manually, against the m-stdlib v0.5.0 manifest): hover on
parse^STDJSONrenders signature + params + raises + source. Cmd-click jumps tosrc/STDJSON.m:39. Typing^STDproduces a completion list of all 32 modules with synopses. - Out of scope for v0.1, deferred to follow-on work: WC2 (snippet pack — see next row); diagnostic surface for
,U-STD*,references in$ETRAPhandlers (would need a richer parse than the current text-based classifier);.vsixpackaging for marketplace distribution (the user cannpx vsce packagewhen they want a publishable artefact); a real M LSP (out of scope for the entire WC track). - Push status: pushed. Repo created via
gh repo create rafael5/m-stdlib-vscode --public --source=. --remote=origin; local branch renamedmaster→mainto match project convention;git push -u origin maincompleted. Live athttps://github.com/m-dev-tools/m-stdlib-vscode.
Status. done (2026-05-08; m-stdlib-vscode commit 7d0723b pushed; v0.2.0 release).
Goal. Canonical idiom snippets. Plan § 5.1.
Approach.
- VS Code snippet JSON for: STDASSERT suite skeleton, STDFIX
withwrapper, STDLOG kv line, STDJSON parse-then-walk. - Trigger names:
stdassert-suite,stdfix-with,stdlog-kv,stdjson-parse.
Acceptance. Each snippet expands into a working, lint-clean M block.
Out of scope. Snippets for every module; cover only the high-frequency idioms.
Progress log.
- 2026-05-08 (m-stdlib-vscode
7d0723b) — Four-snippet pack landed atsnippets/m.json, wired viapackage.jsoncontributes.snippetsforlanguage: m. Triggers match the WC2 row exactly:stdassert-suite(full suite skeleton withstart/@TEST/eq^STDASSERT/reportplus runner-discoverable;@TESTannotation),stdfix-with(one-liner XECUTE inside a TSTART/TROLLBACK scope),stdlog-kv(level-chooser dropdown — INFO/DEBUG/WARN/ERROR/FATAL — followed by event + key + value),stdjson-parse(parse with error-guard +$$type^STDJSONarray check +$orderwalk skeleton). VS Code tabstop syntax with sane defaults; multi-tabstop sync where the same identifier appears in multiple places (e.g.${3:tCase1}referenced by both thedo tCase1line and the label header). - 5 new tests in
tests/snippets.test.tspin: (1) the four prefixes are present; (2) every snippet is scoped tolanguage: m; (3) every body is a non-empty array of strings; (4) prefix↔module correctness (catches "wrong body in wrong slot" mistakes via substring assertion that each snippet body references the named module); (5)package.jsoncontributes.snippetslinkage. 27/27 tests now green (22 prior from WC1 + 5 new). - Implementation calibrations: (a)
$$callin M source has to be JSON-escaped as\\$\\$so VS Code doesn't mistake the$for tabstop syntax — backslashes flow through to the snippet engine which emits literal$$; (b) the.dot-block prefix doesn't need escaping (VS Code only treats$,},\as snippet-special); (c) bumpedpackage.jsonversion 0.1.0 → 0.2.0 since this is a user-visible feature add (no breaking change). - Wave C now COMPLETE: WC1 + WC2 = 2/2 rows. The VS Code surface for m-stdlib is shipped — hover, goto-def, completion, snippets all wired to
dist/stdlib-manifest.json. Cross-repo follow-on remaining: only Wave D (AI skill).
Status. done (2026-05-08; m-stdlib tools/gen-skill.py + dist/skill/*.md committed; live at ~/claude/skills/m-stdlib/ and registered in ~/claude/CLAUDE.global.md).
Goal. A knowledge-skill at ~/claude/skills/m-stdlib/ regenerated from the manifest. Plan § 6.1.
Approach.
- New
tools/gen-skill.mreadsdist/stdlib-manifest.json+dist/errors.jsonand writes:~/claude/skills/m-stdlib/SKILL.md(frontmatter with trigger conditions, prose orientation)~/claude/skills/m-stdlib/manifest-index.md(one line per module + per public label, dense English)~/claude/skills/m-stdlib/patterns.md(5–15 line idioms for STDASSERT / STDFIX / STDLOG / STDJSON)~/claude/skills/m-stdlib/error-codes.md(everyU-STD*code, origin, mitigation)
- CI gate: regenerate the skill, fail on diff (same model as the manifest gate).
Acceptance. Skill files exist and load via the standard skill mechanism. A /skill m-stdlib invocation gives the model the surface needed to write idiomatic m-stdlib code.
Out of scope. Hand-tuning skill prose beyond the generator output. If a passage needs prose embellishment, add it to a "static-prose" partial in the generator so re-runs don't lose it.
Progress log.
- 2026-05-08 — Pivoted from
tools/gen-skill.m(planned) totools/gen-skill.py(Python, mirroringgen-manifest.py's precedent). Same rationale as WA4: there's no clean home for a non-stdlib M routine, Python's stdlib gives clean text generation, and the generator runs against the manifest produced by Python anyway. Open redirect point if the eat-own-dog-food argument outweighs shipping speed. - What landed:
tools/skill-patterns.md(~250 lines) — hand-curated canonical-idiom library (STDASSERT suite skeleton; STDFIXwith/register/invoke; STDMOCK; STDLOG kv + JSON; STDJSON parse-then-walk; STDURL; STDCSV; STDDATE; STDCSPRNG; STDCRYPTO; STDCOMPRESS; STDHTTP; STDARGS; STDFS; STDSNAP). The file is the static input —gen-skill.pycopies it through verbatim intodist/skill/patterns.mdwith a generated header. Edits to the prose go here, NOT to the generated output.tools/gen-skill.py(~265 lines) — readsdist/stdlib-manifest.json+dist/errors.json+tools/skill-patterns.md. Emits four files intodist/skill/:SKILL.md(~4.5 KB) — frontmatter withname,type: knowledge, full description with trigger phrases ("m-stdlib","STDJSON","STDASSERT","STDCRYPTO","STDLOG","$$parse^STD","do start^STDASSERT","^STD"); body is the catalogue of all 32 modules with their synopses (withm-stdlib —prefix stripped for cleaner reading), a "When to use this skill" guide, a "Companion files" pointer table to the other three, the architectural rules, and a quick-start.manifest-index.md(~26 KB) — one entry per module with every public label rendered as\signature` — synopsis, plus a per-moduleraises:` summary line. The compact dense-English reference for "I know the module name; show me the labels."patterns.md(~8 KB) —tools/skill-patterns.mdcopied through with a one-line generated header noting the source.error-codes.md(~3.5 KB) — inverted index overdist/errors.json, grouped by producing module: everyU-STDxxx-NAMEwith the comma-separated list of labels that raise it. The lookup table for$ETRAPhandler disambiguation.
Makefiletargets:skill(regenerate),skill-check(regenerate + diff against committeddist/skill/; mirrorsmanifest-check),skill-install(copy to~/claude/skills/m-stdlib/so Claude can load it as a knowledge skill — one-shot, not in CI).~/claude/CLAUDE.global.mdskills table — added anm-stdlibrow alongside the existing knowledge skills (mumps-language, vista-system, etc.) so the global skills registry sees the new entry.
- Determinism: no timestamps in the generated output; running
make skill-checkaftermake skillexits clean (skill: clean). Drift detection works identically tomanifest-check. - Calibration vs the plan §6.1 sketch: (1) generator is Python not M (same reasoning as WA4); (2)
dist/skill/is the committed staging dir + the CI-gateable artefact, with~/claude/skills/m-stdlib/populated via a separatemake skill-installstep (cleaner than emitting directly to~/claude/, which lives in a different repo); (3) static prose lives intools/skill-patterns.mdand is copied through verbatim, so prose embellishment doesn't get clobbered by re-runs (the plan called this out as a non-negotiable). - Out of scope (deferred): wiring
make skill-checkinto.github/workflows/ci.yml— the gate works locally; CI integration is a small follow-on. Auto-installing on everymake manifest— kept as a separate explicit step so users in CI don't accidentally write to~/claude/. - Wave D status: WD1 done. WD2 (
tools/gen-doctests.m— emit*DOCTST.mfrom@examplelines) remains. The skill at~/claude/skills/m-stdlib/is loadable now: any session that mentions anSTD*symbol triggers it, and the four files give the model the catalogue + idioms + error registry it needs to write idiomatic m-stdlib code on the first attempt.
Status. done.
Goal. Make @example lines executable. Plan § 3.4.
What landed.
tools/gen-doctests.py(~210 LoC, Python — mirrors the gen-manifest / gen-skill family). Readsdist/stdlib-manifest.json, classifies every@exampleagainst a small set of doctest-eligible shapes, emits onetests/STD<MOD>DOCTST.mper module that has at least one accepted example, removes orphaned suites whose modules dropped to zero accepted.- Eligible shapes (the rest are skipped silently —
--verboselists the decisions):- Pattern A (string-equal):
write <expr> ; "<expected>"→do eq^STDASSERT(.pass,.fail,<expr>,"<expected>","doctest: <label>"). - Pattern A-num (numeric-equal):
write <expr> ; <number>→do eq^STDASSERT(.pass,.fail,<expr>,<number>,...)(no quotes). - Pattern A-prefix (substring):
write <expr> ; "<prefix>..."→do contains^STDASSERT(.pass,.fail,<expr>,"<prefix>",...)(handles e.g.STDCRYPTO.sha256whose hash starts withba7816bf...).
- Pattern A (string-equal):
- Free-variable filter (
expression_is_self_contained): an example likewrite $$size^STDFS(path) ; 4096is structurally Pattern A but references the unbound localpath. The classifier strips string/numeric literals,$$label^MODULEopenings,$intrinsicreferences, and^GLOBALreferences, then refuses any expression that still contains a bare identifier. Catches the cases that would otherwise trip M-MOD-024 at lint time and<UNDEF>at run time. ILLUSTRATIVE_LABELSskiplist (6 entries): the residual category — examples that are self-containedwrite … ; "…"shapes but whose expected value is documented as a placeholder rather than a literal output. Each entry carries a one-line reason:STDDATE.now(current wall-clock),STDMOCK.called/STDMOCK.resolve(require prior mock setup),STDOS.cwd/STDOS.hostname/STDOS.user(host-specific). These were surfaced by running the doctest suite on real engine and seeing them fail with shape-correct-but-value-wrong assertions; documenting the skip in code keeps the "why" attached to the entry.- Sanitiser: M label names cannot contain underscores (
_is the concatenation operator), sotDoctest_decodeparses astDoctest_decode(a concat). Generator emits camelCase names instead —tDoctestDecode,tDoctestUrldecode, etc., which match the project's hand-written test-label convention (tEncodeRfcVectors). - Indentation: 8 spaces, not tabs — the tree-sitter-m parser rejects leading tabs even though classic M tolerates them. Discovered by bisection when
m fmtreportedparse error — source did not parse cleanly (4 error nodes)against an early tab-indented draft. - Lint suppression in the generated header:
; m-lint: disable-file=M-MOD-020,M-MOD-001,M-MOD-031— long-routine, line-length, and magic-literal warnings are intentional in doctest suites where the assertion lines bake in literal arguments straight from@example. - 15 emitted suites, 51 generated doctests, all passing on the engine. Suites span: STDB64 (4), STDCRYPTO (1), STDDATE (6), STDFIX (1), STDFMT (1), STDFS (4), STDHEX (3), STDJSON (1), STDMATH (1), STDREGEX (1), STDSEMVER (8), STDSTR (12), STDTOML (1), STDURL (5), STDUUID (2). Full
make test(47 suites including the 32 hand-written + 15 doctest) reports 2562/2562 assertions green. - Makefile targets:
make doctest(regenerate),make doctest-check(drift gate — regenerate + diff against committed copies; mirrorsmanifest-checkandskill-check),make doctest-run(execute the suite viam test tests/STD*DOCTST.m). The runner is a separate target rather than part ofmake checkbecause it requires a live vista-meta engine connection —make checkis the fast lint+fmt+test loop and stays engine-bound only viamake test. Once the doctest invariant is stable, the runner can be folded intomake ci.
Calibration vs plan § 3.4. Plan envisaged "two @example shapes" — a bare-invocation form and a self-asserting do eq^STDASSERT(...) form. Reality: the second form (already-asserting examples) appears almost exclusively in STDASSERT itself and references free counter variables that aren't valid outside an actual test routine, so emitting them verbatim isn't useful. The bare-invocation form is the pivotal case, and we extended it three ways (string / numeric / prefix). The skiplist mechanism for illustrative-only examples is new — the plan didn't anticipate that some write … ; "…" examples are documented placeholder values rather than literal expectations. The Python implementation (matching gen-manifest.py / gen-skill.py) replaces the plan's "tools/gen-doctests.m" so the generator family is uniform.
Out of scope (deferred).
- Wiring
make doctest-checkinto.github/workflows/ci.yml— small follow-on; gate works locally and the file family is committed so drift is visible in PRs anyway. - Source
@examplereformulation — six labels (theILLUSTRATIVE_LABELSskiplist) document a placeholder value rather than a literal expected output. Reformulating their@exampletags to be self-contained (e.g.write $$valid^STDDATE($$now^STDDATE()) ; 1for STDDATE.now) would let us drop the skiplist; deferred because the current docs are clearer with the placeholder shape. - Multi-statement examples — Pattern D in the classification (e.g.
do set rc=$$parse^STDXML(text,.tree)) is a multi-step setup-then-assert idiom that needs richer parsing to emit. Those examples remain illustrative-only for now.
Files touched. tools/gen-doctests.py (new, ~210 LoC), tests/STD{B64,CRYPTO,DATE,FIX,FMT,FS,HEX,JSON,MATH,REGEX,SEMVER,STR,TOML,URL,UUID}DOCTST.m (15 generated, committed), Makefile (3 new targets + .PHONY extension), this tracker.
Wave D status: COMPLETE. WD1 (AI skill) + WD2 (doctest generator) both shipped. The discoverability + tooling plan as a whole is closed at 16/16 rows.
A compact dependency view, useful when planning a session:
Wave A:
WA1 ──┬──► WA2 ──┐
├──► WA3 │
└──► WA4 ──┼──► WA5
├──► WA7 ──┐
WA6 ─┴──────────┴──► WA8
Wave B (depends on WA4):
WA4 ──► WB1 ──► WB2
└─► WB3
└─► WB4 (also needs WA7 for `m errors`)
Wave C (depends on WA4):
WA4 ──► WC1 ──► WC2
Wave D:
WA4 ──► WD1
WA1 + WA2 ──► WD2
The shortest path to a useful new capability is WA1 → WA4 → WB1: once those three are done, every subsequent piece (other Wave A items, the rest of Wave B, all of Wave C, Wave D) becomes easier.