Skip to content

HF-359: Turn the repository into a monorepo, and rebuild the developer documentation - #1757

Draft
sequba wants to merge 28 commits into
developfrom
feat/hf-359-monorepo-agent-setup
Draft

HF-359: Turn the repository into a monorepo, and rebuild the developer documentation#1757
sequba wants to merge 28 commits into
developfrom
feat/hf-359-monorepo-agent-setup

Conversation

@sequba

@sequba sequba commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Context

HF-359. Turns the repository into a monorepo, and replaces the single always-loaded DEV_DOCS.md with a documentation system that survives having more than one package in the tree.

hyperformula/      the engine (published)   — src/, test/, its toolchain, dev-docs/, script/
hyperformula-ui/   placeholder              — imported separately, preserving history
docs/              the portal               — its own package, installs on its own
script/            release/ and the licence gate — the only scripts that span the repo
dev-docs/          repository-wide standards and process

No changes under hyperformula/src/. Every file there is byte-identical to its old location at src/; git records the moves as renames throughout, so history follows. The published package's name, version, entry points and exports map are unchanged, and the publish-package check confirms the tarball contents.

The monorepo

  • npm workspaces, with a private root carrying fan-out scripts. Linting is the deliberate exception — it runs once from the root over the whole repository, so nothing between packages falls through the gap.
  • docs/ left the workspace. VuePress 1.x and its --openssl-legacy-provider tree must never reach an engine install. npm run docs:install installs it; CI installs it before building the portal.
  • The portal owns its deployment: wrangler.jsonc and worker/ moved under docs/, and the asset directory is now ./.vuepress/dist.
  • The packages release together, on one version, from one changelog. A release cuts every published package at the same version whether or not its code moved, in exchange for one number that identifies the state of the whole repository. CHANGELOG.md stays at the root.
  • The private test suite stays branch-matched; only its checkout path moved. Its 501 spec files needed no edit — they import the engine relatively, and because src/ and test/ moved together, the depth from a spec to the package root is unchanged.

Everything lives with what owns it

Repository level Package level
dev-docs/ definition of done, code style, testing standards, documentation rules, build and release, pull requests, worktrees, agent setup hyperformula/dev-docs/ — architecture, parser, interpreter, dependency graph, function catalogue, i18n, performance, its suites, its build
script/ release/, check-licenses.mjs hyperformula/script/ — build checks; docs/script/ — the generator and the Worker assets

Three dev-docs pages were genuinely mixed and were split, not moved: TESTING (what a change must prove stays; the suites go down), BUILD (workspace, deploy and release stay; the intermediate build and packaging go down), and CODE-STYLE (style stays; the hot-path table becomes hyperformula/dev-docs/PERFORMANCE.md).

The script/ split was decided by the callers rather than by judgement: every script had exactly one caller and the caller named its owner. Twelve call sites stopped reaching across a package boundary.

Root examples/ is deleted — three PNGs were byte-identical duplicates of docs/.vuepress/public/, which is what the site actually serves, and the other five files were referenced nowhere.

The documentation

DEV_DOCS.md said "start by reading entire DEV_DOCS.md". Every agent working anywhere loaded the docs-portal conventions, the i18n sources and the function-catalogue rules before it could look at a parser bug. Three layers replace it:

Layer Answers Loaded
AGENTS.md What is this directory, and where do I look next? Always, within its subtree
dev-docs/ How does this work and why? On demand
.claude/skills/ How do I do task X? On skill trigger

dev-docs/ is the single source of truth, and nothing outside it restates what is in it: every AGENTS.md, README.md and SKILL.md carries only what is so specific to its own directory that it would be useless elsewhere, and links for the rest.

Before After
Always-loaded AGENTS.md ~610 lines 147 lines across 12 files
Skills ~1100 lines 378 lines across 9

Twelve AGENTS.md files, each with a CLAUDE.md symlink so Claude Code and Cursor read one source. Nine skills, five scoped by a paths glob. .claude/settings.json enables typescript-lsp repository-wide and denies agent reads of build artifacts — but not of generated documentation, which is meant to be read. .worktreeinclude records what must never be copied into a worktree and why. .ai/ holds one sentence pointing at dev-docs/, for agents that look there by habit.

DOCS_CONTENT_GUIDE.md moved in as dev-docs/DOCS-CONTENT-GUIDE.md.

How did you test your changes?

Command Result
npm ci Clean; the workspace resolves
npm run lint 0 errors, 54011 warnings — the exact pre-move baseline
npm run test:jest 502 suites, 6232 passed, 3 skipped, 0 failed
npm run test:ci -- --coverage 97.42%, written to hyperformula/coverage
npm run test:browser 12464 assertions across headless Chrome and Firefox, exit 0
npm run verify:typings tsc --noEmit clean
npm run bundle-all Every bundle, and Publish package check: OK
npm run check:licenses 3 production dependencies, and it fails when the allowlist is tightened
npm run release code-freeze … (dry run) Correct version, dates and changelog section
npm run docs:generate-function-docs 564 lines, 13 category sections
npm --prefix docs run build:cf 217 pages, sitemap, _headers and _redirects copied

Every relative Markdown link in AGENTS.md, both dev-docs/ levels, the skills and the READMEs was resolved against the filesystem. All resolve.

What the reviews found

Four rounds, 58 findings. The ones worth knowing about:

Flag arguments were being swallowed. Every delegating root script dropped them, so npm run test:ci -- --coverage reached the workspace as bare jest --forceExit and CI had been uploading nothing to Codecov while staying green. Demonstrated with npm run test:jest -- --listTests, which ran all 6232 tests instead of listing them.

The release script was never adapted. It read and wrote ./package.json — now the private root manifest with no version — and published from the root, where npm refuses a private package. Two dry runs surfaced two further paths no review had caught.

check:licenses was checking nothing. license-checker reads <start>/node_modules, and npm hoists, so from hyperformula/ it saw an empty tree and from the root a manifest with no dependencies — zero packages either way, passing vacuously. Replaced with script/check-licenses.mjs, driven by npm ls --omit=dev.

The portal was publishing internal agent instructions. VuePress sweeps every markdown file under docs/, so docs/AGENTS.md and its symlink were being built into /AGENTS.html and an identical /CLAUDE.html, sitemap and search index included.

npm run test:browser exited non-zero after passing. The cause was client.clearContext: false in the Karma base config, and it reproduced on the pre-move tree. concurrency: 1 was the obvious first guess and is wrong — it doubles wall-clock time and makes the failure deterministic rather than removing it.

An audit traced the deleted DEV_DOCS.md into the new tree. Almost nothing was lost; what it found was new prose that had invented things. dev-docs/README.md stated a link convention that every one of the 33 links in the directory violated, and I18N.md claimed argument separators are part of the language package when they are ConfigParams.

One finding was rejected on measurement — a claimed ESLint timeout risk, measured at 0.91s and 1.06s.

Two pre-existing problems this uncovered

The source language packs have never been linted. The old ignore list carried a bare languages entry meant for the build output. An unanchored pattern matches a directory of that name at any depth, so it also excluded src/i18n/languages/ — and the sort-keys override targeting exactly those files has never run. Anchoring the build-output entry exposed 881 violations across 19 translation files. Confirmed against the pre-move tree, where ESLint reports File ignored because of a matching ignore pattern.

They are excluded again in .eslintignore, deliberately and with a comment, so this branch keeps the exact pre-move lint baseline. Sorting 19 translation files is a change of its own — and reordering translation entries is what the i18n guidance tells contributors not to do casually.

npm run test:compatibility was broken for everyone, pointing at a path in neither repository. Fixed here, since the branch had to touch it anyway.

Not done, on purpose

CI path filtering. A naive paths: filter on a workflow that branch protection lists as a required check leaves it permanently pending and makes pull requests unmergeable. Doing it safely needs the required-checks list, which lives in repository settings, plus the dorny/paths-filter-and-single-gate shape. The CI paths are all updated; only the filtering is outstanding.

Importing hyperformula-ui — left for the teammate who owns the formula-builder move. It is deliberately not in the workspaces array until then: npm silently ignores an entry with no package.json.

Both are recorded in dev-docs/STRUCTURE.md under "What the move still owes", so they survive the pull request.

Types of changes

  • Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore)
  • New feature or improvement (a non-breaking change that adds functionality)
  • Bug fix (a non-breaking change that fixes an issue)
  • Additional language file, or a change to an existing language file (translations)
  • Change to the documentation

Repository structure and tooling. The published package is unchanged.

Related issues:

  1. HF-359

Checklist:

  • I have reviewed the guidelines about Contributing to HyperFormula and I confirm that my code follows the code style of this project.
  • I have signed the Contributor License Agreement.
  • My change is compliant with the OpenDocument standard. — not applicable; no engine behaviour changes.
  • My change is compatible with Microsoft Excel. — not applicable; no engine behaviour changes.
  • My change is compatible with Google Sheets. — not applicable; no engine behaviour changes.
  • I described my changes in the CHANGELOG.md file. — intentionally not done. Nothing user-facing changed; the published package is identical.
  • My changes require a documentation update. — dev-docs/ at both levels, docs/README.md and the public building guide are updated here.
  • My changes require a migration guide.

Companion pull request

handsontable/hyperformula-tests#51 gives the private test suite an AGENTS.md pointing here, and corrects three claims in its README.md.

🤖 Generated with Claude Code

sequba and others added 4 commits August 31, 2026 13:42
…ions

Prepare the repository for the monorepo migration by moving from a single
always-loaded DEV_DOCS.md to a three-layer documentation model:

- AGENTS.md files carry the rules that must not be violated, plus a map of
  where to look next. They load automatically in their own subtree, so an
  agent working in src/parser/ never pays for the docs portal's conventions.
  Added at the repository root and in src/, src/parser/, src/interpreter/,
  src/interpreter/plugin/, src/interpreter/functionMetadata/,
  src/DependencyGraph/, src/i18n/, docs/, test/, and script/. In each
  directory CLAUDE.md is a symlink to the sibling AGENTS.md, so Claude Code
  and Cursor read one source.
- dev-docs/ holds the deep reference, loaded on demand: structure,
  architecture, build, testing, definition of done, code style, doc
  standards, the function metadata catalogue, i18n, worktrees, and the
  target monorepo layout. DEV_DOCS.md becomes an index into it.
- .claude/skills/ holds nine task workflows, scoped with the paths
  frontmatter field so each loads only where it applies.

Also add .worktreeinclude, which documents why the branch-matched private
test suite and the build outputs must never be copied into a worktree, and
.claude/settings.json, which enables the typescript-lsp plugin repository-wide
and keeps build output out of agent reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The build outputs are git-ignored, so content searches already skip them, but
nothing stopped an agent from opening dist/hyperformula.js directly or from
answering a question about behaviour from typings/ instead of src/. Extend the
deny list to cover every generated path in .gitignore.

node_modules/ and package-lock.json are deliberately left readable: reading a
dependency's source is occasionally the right move when debugging, and a deny
rule would also block a targeted grep for a dependency version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add script/claude/post-tool-use.mjs, wired to Edit|Write in
.claude/settings.json. Claude Code passes the tool payload as JSON on stdin;
the hook reads the edited path, lints that one file, and exits 2 with whatever
errors remain, which Claude Code shows to the agent. ESLint rules are then
applied while the change is still being written, instead of surfacing when
someone runs `npm run lint` at the end.

Three properties are deliberate:

- It reports errors only. `npm run lint` produces tens of thousands of
  warnings across the repository, and reporting them per edit would bury the
  agent in noise unrelated to its own change.
- It passes `--fix-type problem,layout`, never a plain `--fix`. The
  jsdoc/require-jsdoc rule autofixes by inserting an EMPTY JSDoc block above
  every undocumented declaration, so an unconstrained --fix quietly scatters
  those stubs through any file the agent touches. The restriction keeps the
  fixes that are unambiguously right (licence header, semicolons, quotes,
  spacing) and leaves the rest to a human.
- It fails open. A missing binary, a spawn failure, or unparseable output
  exits 0 in silence, because a broken hook must never block work.

Also record three decisions in the monorepo plan: the repository stays on npm
workspaces, the private test suite stays branch-matched and only changes its
checkout path, and every package versions and releases on its own cadence,
which implies a changelog per package and package-namespaced tags.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every package versions and releases on its own cadence, so the plan now calls
for a changelog per package, package-namespaced tags, and a release workflow
parameterised by package. The UI packages keep the @hfe/* scope they publish
under today: moving them between repositories is enough change for one
migration, and a rename would break every existing consumer's imports for no
benefit the move itself delivers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qunabu

qunabu commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 31, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
hyperformula-docs b562c14 Sep 01 2026, 01:58 PM

Four decisions, and one small correctness fix on the way through.

- The Cloudflare Worker moves under docs/. wrangler.jsonc and worker/index.js
  exist only to serve the built portal, and docs/ is leaving the workspace
  anyway, so the whole deployment path ends up in one directory that installs
  and versions on its own.
- CHANGELOG.md keeps its current Keep a Changelog form, one file per package.
  No JSON fragment mechanism: fragments solve merge conflicts this repository
  does not have yet, and introducing them now would cost a tool, a CI gate,
  and a habit change for a problem that has not arrived.
- Every .nvmrc says 22, docs/ included once it has its own.
- The HyperFormula agent skill stays in the shared skills repository for now.
  Bringing it here is a separate decision taken later; nothing in the target
  layout depends on it.

The fix: test/fetch-tests.sh called the directory holding test/ REPO_ROOT.
That is true today and stops being true once src/ and test/ move a level down,
where it becomes the package root instead. Renamed to PACKAGE_ROOT, with a
comment recording that all the script needs from it is a path inside the git
working tree, which `git rev-parse` resolves from any depth. Verified by
running `npm run test:setup-private`.

Also correct docs/README.md, which still asked for Node.js 14+.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

Performance comparison of head (0c2372b) vs base (286a731)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |  493.89 |  511.73 | +3.61%
                                      Sheet B |  155.55 |  167.03 | +7.38%
                                      Sheet T |   138.2 |   143.1 | +3.55%
                                Column ranges |  471.76 |  477.57 | +1.23%
                                Sorted lookup | 14410.1 | 14559.5 | +1.04%
Sheet A:  change value, add/remove row/column |   17.19 |   16.31 | -5.12%
 Sheet B: change value, add/remove row/column |   139.6 |  151.57 | +8.57%
                   Column ranges - add column |  151.99 |  163.79 | +7.76%
                Column ranges - without batch |  468.89 |  480.09 | +2.39%
                        Column ranges - batch |  119.16 |  121.43 | +1.91%

Comment thread dev-docs/BUILD.md Outdated
Comment thread dev-docs/MONOREPO.md Outdated
Comment thread dev-docs/MONOREPO.md Outdated
Comment thread dev-docs/MONOREPO.md Outdated
Comment thread DEV_DOCS.md Outdated
Comment thread hyperformula/src/interpreter/plugin/AGENTS.md
Comment thread hyperformula/src/interpreter/AGENTS.md
Comment thread hyperformula/src/parser/AGENTS.md
Comment thread hyperformula/src/AGENTS.md
Comment thread hyperformula/test/AGENTS.md
sequba and others added 3 commits August 31, 2026 15:16
Review feedback. Every AGENTS.md, README.md, and SKILL.md now carries only
what is so specific to its own context that it would be useless anywhere else;
everything else is a link into dev-docs/. Two copies of a rule means one of
them is wrong within a release, and the reader cannot tell which.

The always-loaded surface drops from roughly 610 lines to 147. Each
per-directory AGENTS.md is now six to ten lines: what the directory is, and
where the detail lives. The nine skills drop from about 1100 lines to 273,
keeping the steps and the ordering and linking out for the rules those steps
enforce.

The content moved rather than being deleted. dev-docs/ gains PARSER.md,
INTERPRETER.md (including the whole built-in function contract),
DEPENDENCY-GRAPH.md, PULL-REQUESTS.md, and AGENT-TOOLING.md; ARCHITECTURE.md
absorbs the engine-wide invariants, TESTING.md the test-writing discipline,
DOC-STANDARDS.md the changelog rules, FUNCTION-CATALOGUE.md and I18N.md their
respective rule lists. script/README.md is new, and docs/README.md now
describes the portal it lives in.

BUILD.md no longer restates docs/guide/building.md. It points there for the
command reference and keeps only what the public guide omits: the intermediate
lib/ build, HF_COMPILE, the generated documentation and the rule against
committing it, the Cloudflare deployment path, release, and packaging.

MONOREPO.md drops the rationale and open-questions sections - the plan is
worth keeping, the record of how it was decided is not - and hyperformula-ui
is now one package rather than four.

DEV_DOCS.md is removed; dev-docs/README.md is the front page. .ai/ exists with
a single sentence pointing at dev-docs/, for agents that look there by habit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 1 of all nine skills is now "Read the relevant files from dev-docs/",
followed by a table of the specific pages with one line each saying why that
page matters for this task. Not a general pointer at the directory: a skill
that says "the rules live in dev-docs" invites an agent to skip the reading and
work from the steps alone, which is exactly the failure the single-source rule
exists to prevent.

Where the reference depends on what the task touches, the step lists the
always-read pages first and the conditional ones after: hyperformula-dev reads
ARCHITECTURE, CODE-STYLE, and DEFINITION-OF-DONE every time, then the page for
the pipeline stage being changed. hyperformula-code-review does the same with
DEFINITION-OF-DONE, CODE-STYLE, and TESTING.

The remaining steps are renumbered to follow, so every skill now reads as one
ordered procedure rather than a set of sections.

The convention is recorded in dev-docs/AGENT-TOOLING.md, so the next skill
written here inherits it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DOCS_CONTENT_GUIDE.md was the last piece of internal documentation sitting
outside dev-docs/, which made it the one page an agent had to be told about
separately. It is now dev-docs/DOCS-CONTENT-GUIDE.md, named like its
neighbours, and reachable from the index like everything else.

The move exposed a contradiction between it and DOC-STANDARDS.md, which is
exactly what having two homes for documentation rules produces. DOC-STANDARDS
said a guide should not duplicate the API reference; the content guide says
self-containment beats DRY and a page must let the reader finish the task
without leaving it. Both are right about different things, so the two pages now
divide the work explicitly: DOC-STANDARDS covers WHEN documentation is required
and what must be true of it, DOCS-CONTENT-GUIDE covers HOW to write the page,
and DOC-STANDARDS defers to it on the trade-off rather than restating a
competing rule.

Also repair four checklist items whose continuation lines were wrapped in stray
code fences, so half of each item rendered as a code block.

References updated in docs/AGENTS.md, docs/README.md, dev-docs/README.md,
dev-docs/STRUCTURE.md, and the writing-docs-pages skill, where the guide moves
into the step-1 reading table and the self-review checklist becomes an explicit
closing step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread AGENTS.md Outdated
Comment thread AGENTS.md Outdated
Comment thread .claude/skills/changelog-creation/SKILL.md Outdated
Comment thread dev-docs/STRUCTURE.md
Four threads.

The routing table has moved out of AGENTS.md and into dev-docs/README.md, where
it replaces the categorised file listing rather than sitting beside it. The two
were the same information keyed differently, which is exactly the duplication
the single-source rule exists to prevent. AGENTS.md now points at that page and
says nothing about which page covers what. The line describing how directory
AGENTS.md files load is folded into the same sentence rather than trailing the
table it belonged to.

STRUCTURE.md and MONOREPO.md are one file. Both carried a tree, and a reader had
to know which of the two was current; STRUCTURE.md now covers where everything
lives today and, under "Where it is going", the packages, the target tree and
the migration steps. MONOREPO.md is deleted and its two inbound links repointed.

The changelog skill states the link rule where it is acted on. It said only that
"the entry carries a link" at the step where the number is obtained, and named
the issue-or-pull-request rule two steps later; both steps now say it outright.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sequba
sequba force-pushed the feat/hf-359-monorepo-agent-setup branch from efb7b74 to 80753c5 Compare September 1, 2026 08:17
sequba and others added 4 commits September 1, 2026 10:33
Removes script/claude/post-tool-use.mjs, its registration in
.claude/settings.json, and the documentation that described it.

The hook linted every file an agent wrote and reported the remaining errors
back. It worked, but it is not something this repository has to carry: lint
still runs from the root on demand and in CI, which is where the gate belongs,
and the hook added a per-edit cost and a file that rewrote the agent's work
underneath it for a convenience nobody asked for.

script/ now holds only build, docs-generation and release scripts, and
dev-docs/AGENT-TOOLING.md covers settings and skills alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An audit traced the deleted DEV_DOCS.md and the rewritten AGENTS.md into the new
tree. Almost nothing was lost; what it found instead were assertions the new
prose invented, and rules that the split left in two places where they promptly
disagreed.

dev-docs/README.md told the reader to write repository-root-relative link
targets. The directory it governs contains 33 filesystem-relative link targets
and not one root-relative one, eight of them in that same file. An agent obeying
the stated rule writes links that resolve nowhere. The convention now describes
what the tree does, separating link targets from paths named in prose.

"The five places a function change must touch" was a term of art naming two
different sets: INTERPRETER.md omitted the changelog and counted
implementedFunctions separately, while the review skill counted the changelog
and folded implementedFunctions in. INTERPRETER.md is now the one list, and both
skills link to it instead of keeping a copy. The same list claimed all five
omissions fail silently, contradicting FUNCTION-CATALOGUE.md, which correctly
says a missing catalogue entry fails the docs build; it now states the split.

Two file locations were wrong in the same way, and last round only one of them
was fixed: absolutizeDependencies.ts and LazilyTransformingAstService.ts both
live at the source root, not in the parser and dependency-graph directories that
listed them.

I18N.md claimed argument separators are part of the language package. They are
configuration - functionArgSeparator and its neighbours in ConfigParams - and
nothing in the old documentation said otherwise.

Also corrected: test:compatibility pointed at a path in neither repository, and
the prose blamed its failure on a missing private suite; BUILD.md presented the
portal deploy commands with no hint that deployment is automated on push and
these exist for debugging; AGENT-TOOLING.md said every skill is scoped by a
paths glob, when four of nine deliberately are not; STRUCTURE.md inventoried a
.claude/agents/ that does not exist; the root AGENTS.md stated the single-source
rule without the "only what is specific to its own context" exception its two
other copies carry; CODE-STYLE.md claimed everything below its intro is what
ESLint cannot check, while the jsdoc rules are enabled, and had lost the deep
link to the linter section of the building guide; docs/README.md still said
npm install where BUILD.md says npm ci; and DOC-STANDARDS.md stated the
changelog exemption twice with two different scopes.

The rule that agents should read the repository's own documentation rather than
fetching the rendered portal was dropped in the rewrite and is restored.

Two findings were not acted on: one describes the PostToolUse hook, which has
since been deleted, and one is a wording quibble about which generator runs
first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The monorepo plan had every package versioning and releasing on its own
cadence, each with its own CHANGELOG.md. Reversed: the packages release
together on one version, and CHANGELOG.md at the repository root stays the
single history for all of them.

One number to reason about — the version a user reports a bug against
identifies the state of the whole repository — at the cost of publishing a
package whose code did not move. Entries name the package they concern when the
text does not make it obvious.

The published package still needs a changelog inside its tarball, so the plan
now says the release copies the root file in at pack time rather than keeping a
second copy under version control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
### Context

Turns the repository into a monorepo (HF-359). Stacked on #1757 — review
that first; this branch is based on it.

```
hyperformula/      the engine (published)   — src/, test/, its toolchain, dev-docs/, script/
hyperformula-ui/   placeholder              — imported separately, preserving history
docs/              the portal               — own package, installs on its own
script/            release/ and the licence gate — the only things that span the repo
dev-docs/          repository-wide standards and process
```

**No changes under `hyperformula/src/`.** Every file there is
byte-identical to its old location at `src/`; git records the moves as
renames throughout, so history follows. The published package's name,
version, entry points and `exports` map are unchanged, and the
publish-package check confirms the tarball contents.

#### The layout

- **npm workspaces**, with a private root carrying fan-out scripts.
**Linting is the deliberate exception** — it runs once from the root
over the whole repository, so nothing between packages falls through the
gap.
- **`docs/` left the workspace.** VuePress 1.x and its
`--openssl-legacy-provider` tree must never reach an engine install.
`npm run docs:install` installs it; CI installs it before building the
portal.
- **The portal owns its deployment**: `wrangler.jsonc` and `worker/`
moved under `docs/`, and the asset directory is now `./.vuepress/dist`.
- **Per-package `.nvmrc` (all `22`) and `CHANGELOG.md`.** The root
changelog is an index.
- **The private test suite stays branch-matched**; only its checkout
path moved. Its 501 spec files needed no edit — they import the engine
relatively, and because `src/` and `test/` moved *together*, the depth
from a spec to the package root is unchanged.

#### Everything lives with what owns it

Three directories are scoped by ownership rather than by convenience:

| | Repository level | Package level |
|---|---|---|
| `dev-docs/` | definition of done, code style, testing standards,
documentation rules, build and release, pull requests, worktrees, agent
setup | `hyperformula/dev-docs/` — architecture, parser, interpreter,
dependency graph, function catalogue, i18n, performance, its suites, its
build |
| `script/` | `release/`, `check-licenses.mjs` | `hyperformula/script/`
— build checks; `docs/script/` — the generator and the Worker assets |

Three `dev-docs` pages were genuinely mixed and were **split, not
moved**: `TESTING` (what a change must prove stays; the suites go down),
`BUILD` (workspace, deploy and release stay; the intermediate build and
packaging go down), and `CODE-STYLE` (style stays; the hot-path table
becomes `hyperformula/dev-docs/PERFORMANCE.md`).

The `script/` split was decided by the callers, not by judgement: every
script had exactly one caller and the caller named its owner. Twelve
call sites stopped reaching across a package boundary, and two scripts
stopped resolving paths through `../hyperformula/` and `../docs/`.

`examples/` at the root is **deleted**: three of its PNGs were
byte-identical duplicates of `docs/.vuepress/public/`, which is what the
site actually serves through `$withBase`, and the remaining five files
were referenced nowhere in the repository, in any config, workflow or
package script. It was not moved under `docs/`, because `docs/examples/`
already exists and is a different thing — the runnable examples embedded
in the guides.

#### Three things the move forced

**The built-in-functions generator moved to `docs/script/`.** It uses
`@vuepress/shared-utils` for slugs, and that package `require()`s
`markdown-it-emoji` and an older `lru-cache` major without declaring
either, relying on `vuepress` to hoist them. Outside a full VuePress
tree it throws on load, so the generator has to live where that tree is.

**Three portal paths were written from the old repository root** and
broke once VuePress built from `docs/` as its own package. All three are
now anchored explicitly rather than depending on cwd:

| Path | Was | Symptom |
|---|---|---|
| code-snippet plugin | fell back to `process.cwd()` | looked in
`docs/docs/examples/`, so every guide with an example failed to compile
|
| `graph.vue` | `../../../dist/hyperformula.full` | resolved to a
`dist/` at the repository root that no longer exists |
| `dest` | `docs/.vuepress/dist/docs` | wrote the built site to
`docs/docs/` |

**One decision was reversed.** The migration first kept a single
`dev-docs/`, on the grounds that two directories would fragment the
single source of truth. That conflated a *single source* with a *single
directory* — splitting by ownership is not duplication, and keeping
every package's internals at the root would have made the root a dumping
ground the moment `hyperformula-ui` lands.

### How did you test your changes?

Everything below was run on this branch, and re-run after each review
round:

| Command | Result |
|---|---|
| `npm ci` | Clean; the workspace resolves |
| `npm run lint` | **0 errors**, 54011 warnings — the exact pre-move
baseline |
| `npm run test:jest` | **502 suites, 6232 passed**, 3 skipped, 0 failed
|
| `npm run test:ci -- --coverage` | **97.42%**, written to
`hyperformula/coverage` |
| `npm run test:browser` | **12464 assertions** across headless Chrome
and Firefox, exit 0 |
| `npm run verify:typings` | `tsc --noEmit` clean |
| `npm run bundle-all` | Every bundle, and **`Publish package check:
OK`** |
| `npm run check:licenses` | 3 production dependencies, and it **fails**
when the allowlist is tightened |
| `npm run release code-freeze …` (dry run) | Correct version, dates and
changelog section |
| `npm run docs:generate-function-docs` | 564 lines, 13 category
sections |
| `npm --prefix docs run build:cf` | **217 pages**, sitemap, `_headers`
and `_redirects` copied |

Every relative Markdown link in `AGENTS.md`, both `dev-docs/` levels,
the skills and the READMEs was resolved against the filesystem. All
resolve.

### What the reviews found

Two rounds, 36 findings. What they caught, and what I got wrong:

**Flag arguments were being swallowed.** Every delegating root script
dropped them, so `npm run test:ci -- --coverage` reached the workspace
as bare `jest --forceExit` and CI had been uploading nothing to Codecov
while staying green. Demonstrated with `npm run test:jest --
--listTests`, which ran all 6232 tests instead of listing them.

**The release script was never adapted.** It read and wrote
`./package.json` — now the private root manifest with no `version`. A
dry run then surfaced a third path the review had not caught: the
private test suite check still pointed at `test/hyperformula-tests`.

**`check:licenses` was checking nothing.** `license-checker` reads
`<start>/node_modules`, and npm hoists, so from `hyperformula/` it saw
an empty tree and from the root a manifest with no dependencies — zero
packages either way, passing vacuously. Replaced with
`script/check-licenses.mjs`, which takes the production set from `npm ls
--omit=dev` and the licence data from a full `license-checker` run.

**The portal was publishing internal agent instructions.** VuePress
sweeps every markdown file under `docs/`, so `docs/AGENTS.md` and its
`CLAUDE.md` symlink were being built into `/AGENTS.html` and a
byte-identical `/CLAUDE.html`, sitemap and search index included.

**`npm run test:browser` exited non-zero after passing.** All 6232 specs
passed in both browsers and always did; Chrome then reported `Some of
your tests did a full page reload!` during teardown. The cause was
`client.clearContext: false` in the Karma base config, and it reproduced
on the pre-move tree, so it predates this branch. `concurrency: 1` was
the obvious first guess and is wrong — it does not remove the error, it
doubles wall-clock time, and it turns an intermittent failure into a
deterministic one. That determinism was what made the real fix testable,
but it is not in the branch.

Also fixed: the report path in `performance.yml`,
`code-examples-generator.sh` silently processing zero files,
`docs:build:cf` never installing the portal, `docs:install` using `npm
install` against a committed lockfile, the licence-header rule resolving
against the working directory, `hyperformula-ui` listed as a workspace
with no `package.json`, a dead `.npmignore` rule, and a dozen
documentation pages naming commands or paths this branch had moved.

One finding was **rejected on measurement** — the claim that the
type-aware ESLint program build risks the hook's 60 s timeout. Measured
at **0.91 s** and **1.06 s**. That hook has since been removed anyway,
at the author's request.

### Two pre-existing problems the move uncovered

**The source language packs have never been linted.** The old ignore
list carried a bare `languages` entry meant for the build output. An
unanchored pattern matches a directory of that name at *any* depth, so
it also excluded `src/i18n/languages/` — and the `sort-keys` override
that targets exactly those files has therefore never run. Anchoring the
build-output entry exposed **881 violations** across 19 translation
files. Confirmed by linting `src/i18n/languages/enGB.ts` on the pre-move
tree, where ESLint reports `File ignored because of a matching ignore
pattern`.

They are excluded again in `.eslintignore`, deliberately and with a
comment explaining why, so this branch keeps the exact pre-move lint
baseline. Sorting 19 translation files is a change of its own — and
reordering translation entries is precisely what the i18n guidance tells
contributors not to do casually.

**`npm run test:compatibility` was broken for everyone.** It pointed at
`test/compatibility/test-compatibility.sh`, a path that exists in
neither repository; the script ships with the private suite. Fixed here,
since the branch had to touch it anyway.

### Not done, on purpose

**CI path filtering.** A naive `paths:` filter on a workflow that branch
protection lists as a required check leaves the check permanently
pending and makes pull requests unmergeable. Doing it safely needs the
required-checks list, which lives in repository settings rather than in
the tree, plus the `dorny/paths-filter`-and-single-gate shape the
Handsontable monorepo uses. The CI *paths* are all updated; only the
filtering is outstanding.

**Importing `hyperformula-ui`** — left for the teammate who owns the
formula-builder move. It is deliberately *not* in the `workspaces` array
until then: npm silently ignores an entry with no `package.json`, and
the lockfile has to be regenerated when the real package lands either
way.

Both are recorded in `dev-docs/STRUCTURE.md` under "What the move still
owes".

### Types of changes

- [ ] Breaking change (a fix or a feature because of which an existing
functionality doesn't work as expected anymore)
- [x] New feature or improvement (a non-breaking change that adds
functionality)
- [ ] Bug fix (a non-breaking change that fixes an issue)
- [ ] Additional language file, or a change to an existing language file
(translations)
- [x] Change to the documentation

Repository structure and tooling. The published package is unchanged.

### Related issues:
1. HF-359

### Checklist:
- [x] I have reviewed the guidelines about [Contributing to
HyperFormula](https://hyperformula.handsontable.com/docs/guide/contributing.html)
and I confirm that my code follows the code style of this project.
- [x] I have signed the [Contributor License
Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2).
- [x] My change is compliant with the
[OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html)
standard. — not applicable; no engine behaviour changes.
- [x] My change is compatible with Microsoft Excel. — not applicable; no
engine behaviour changes.
- [x] My change is compatible with Google Sheets. — not applicable; no
engine behaviour changes.
- [ ] I described my changes in the
[CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md)
file. — **intentionally not done.** Nothing user-facing changed; the
published package is identical.
- [x] My changes require a documentation update. — `dev-docs/` at both
levels, `docs/README.md` and the public building guide are updated in
this change.
- [ ] My changes require a migration guide.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@sequba sequba changed the title HF-359: Split the developer docs and add per-directory agent instructions HF-359: Turn the repository into a monorepo, and rebuild the developer documentation Sep 1, 2026
… file

Each of these links resolved to a file that exists, so no link checker
flagged them; they just did not point where their label said.

- `hyperformula/AGENTS.md:5` targeted `../dev-docs/README.md`, the
  repository-level index that line 6 already links. The package-level
  index — the only page listing the architecture, parser, interpreter,
  dependency-graph, function-catalogue, i18n and performance references —
  was therefore reached from nothing that loads automatically.
- Both halves of the BUILD split targeted a bare `BUILD.md`, which
  resolves to the page doing the linking. Neither level could reach the
  other, so a reader needing the intermediate build and packaging (engine
  page) together with install, deploy and release (repository page) had no
  path between them.
- `dev-docs/BUILD.md:35` named `script/prepare-cf-assets.js`. Spelled from
  the repository root, as this directory's convention requires, that is the
  repository-level `script/`, which holds only `release/` and the licence
  gate. The file is `docs/script/prepare-cf-assets.js`, as line 25 of the
  same page already says.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
… packaging check

- `hyperformula/dev-docs/TESTING.md:5` targeted a bare `TESTING.md`, which
  resolves to the page doing the linking, so the repository-level testing
  standards — what a change must prove, how a case is written, and the ways
  a run must never be made green — were unreachable from the page an agent
  working in `hyperformula/test/` is routed to. Same defect as the two
  BUILD pages.
- The packaging section read as though `verify:publish-package` sat outside
  the default path. `verify-bundles` is `run-p verify:**`, whose glob
  matches it, and `bundle-all` ends in `verify-bundles`, so every
  `bundle-all` already runs `npm pack` through the check. Say so, and keep
  the standalone case for a change that does not rebuild.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
…s them

`hyperformula/dev-docs/TESTING.md:22` promised how the fetch works and the
environment variables it honours, and targeted the repository-level
`dev-docs/README.md`, which documents neither. The mechanics are in
`hyperformula/test/README.md`, the file the link was already labelled with.
The `WORKTREES.md` link on the same line was correct and is unchanged.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
`docs/AGENTS.md:5` promised what the directory contains, what is generated,
and how to run and extend it, and targeted the repository-level
`dev-docs/README.md` — which line 8 already links for everything else. The
portal's own `README.md` is what carries that, including the deployment
section `dev-docs/BUILD.md` defers to.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
claude and others added 11 commits September 1, 2026 11:57
…tead of a README

- `.claude/skills/hyperformula-unit-testing/SKILL.md:14` promised how the
  private suite is fetched and the environment variables it honours, and
  targeted the repository-level `dev-docs/README.md`. With the two engine
  pages already corrected, the file that documents the branch-matched suite
  is now reachable from every place that offers it.
- `dev-docs/DOC-STANDARDS.md:66` sent an author to the portal's README for
  VuePress mechanics through a bare `README.md`, which from `dev-docs/`
  resolves to this directory's own index.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
- `DOC-STANDARDS.md:27` sent a reader with a missing `docs/api/` to
  `npm run docs:build`, which regenerates the function docs, builds and
  verifies every bundle and then builds the whole VuePress site. The folder
  is written by `typedoc:build-api` alone, which the table three lines above
  already names as its generator.
- `STRUCTURE.md:114` recorded the opposite of what the branch ships: it said
  a single root `dev-docs/` had been chosen and an engine-scope copy
  dropped. The two-level split is what exists, is drawn in this file's own
  tree and explained under "`dev-docs/` at two levels", so the decision
  bullet now records that and links to it. Nothing else in the tree claimed
  a single directory.
- `STRUCTURE.md:47` described `.claude/` as holding hooks. It holds
  `settings.json` and `skills/`; the PostToolUse lint hook was removed in
  83eb116 and `AGENT-TOOLING.md` already documents only the plugin list and
  the deny rules.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
…the metadata tables

- Both skills linked a `TESTING.md` anchor at the wrong level, in opposite
  directions: the function-dev skill wanted "what each kind of change needs",
  which only the engine page has, and the code-review skill wanted "a test
  must prove behaviour", which only the repository page has. Each now points
  at the level that carries its heading. Every anchored cross-reference in
  the skills, both dev-docs levels and the AGENTS.md files now resolves.
- Three pages called the public API surface `HyperFormula.ts` "and the types
  it exports". That file exports one class; the types are re-exported from
  `src/index.ts`, which is what the typings build emits. Someone auditing a
  type change for a break was being pointed at the wrong file.
- The metadata tables omitted `enableArrayArithmeticForArguments` and
  `emptyAsDefault`, and said nothing about `arrayFunction` and
  `arraySizeMethod` being deprecated since 3.1.0 in favour of them and of
  `sizeOfResultArrayMethod`. Every field the interface declares is now
  either in a table or named as superseded, which is what the function-dev
  skill promises of this page.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
- `PERFORMANCE.md` opened with the same sentence twice, an artefact of the
  CODE-STYLE split; the copy that carries the cross-link stays.
- The convention paragraph in `dev-docs/README.md` illustrated both of its
  rules with paths this move deletes — `../test/README.md` as a link target
  and `src/interpreter/plugin/` as a prose path. It is the paragraph an
  author is sent to before writing a link, so the examples were being copied
  forward.
- `ARCHITECTURE.md` named `absolutizeDependencies.ts`, `parser/` and
  `LazilyTransformingAstService.ts` without the `hyperformula/` prefix every
  other path in the file uses. The directory attributions were already
  right; only the prefix was stale.

The same leftover remains at `hyperformula/dev-docs/PARSER.md:14`, which was
outside the reported finding.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB3b2sbMKo2Zxf8GHojkjc
The dashboard build command is `npm run docs:build:cf`, a root script: it
needs `bundle-all` and `typedoc:build-api`, which only exist at the root.
Workers Builds uses one root directory for the build and the deploy, so the
deploy commands run from the root too — and `wrangler.jsonc` moved into
`docs/` in this branch. `npx wrangler deploy` at the root therefore finds no
configuration, and no local `wrangler` either, since it is a `docs/`
devDependency.

The documented commands are now the root scripts that already wrap it:
`docs:deploy:cf` and `docs:preview:cf`. Three places said otherwise — the
trigger table, the dashboard settings paragraph, and the header comment in
`wrangler.jsonc` — and the paragraph between the first two claimed the
commands run "from `docs/`", which the dashboard has no way to arrange
alongside a root build command. The settings paragraph now also records the
root directory, the setting that ties the two halves together.

The hand-run block had the same defect in `npx wrangler dev`. Added
`dev:cf` in `docs/`, and `docs:dev:cf` at the root, mirroring the existing
`deploy:cf`/`preview:cf` pair, so every command in that block is a root
script that works from a fresh checkout.

The Cloudflare dashboard still has to be changed to match; this commit only
makes the repository describe the configuration that can work.

Documentation and tooling only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ngxDaE2MeedcUvki2g4wN
`.gitignore` and `STRUCTURE.md` both described a `prepack` that copies the
root `CHANGELOG.md` into the package and a `postpack` that removes it again.
Neither script existed. The only copy was the explicit `copy-changelog`,
called by `verify:publish-package` and by the release script just before it
publishes — so the real release path was safe, but `npm publish
--workspace=hyperformula` or a bare `npm pack` shipped a tarball with no
changelog, which is the one file `check-publish-package.js` lists in
FILES_CHECKLIST and `.npmignore` un-ignores by name.

`prepack` and `postpack` now exist and do what both texts already claimed, so
the guarantee holds for every path that packs or publishes the package rather
than only for the release script.

`npm pack` gained `--silent` in `verify:publish-package`. npm writes its
lifecycle banners to stdout, not stderr, so without it the new hooks would
print four `> hyperformula@3.4.0 prepack` lines into the pipe that feeds
`check-publish-package.js`, which takes its whole first stdin chunk as the
tarball name and would fail to open it.

The explicit `copy-changelog` in `verify:publish-package` is kept, redundant
though it now is: a prepack failure inside `npm pack | node ...` is swallowed
by the pipeline exit status, so that leading `&&` is what still makes the
check fail loudly when the changelog cannot be copied.

Verified by packing the real package: the tarball carries `package/CHANGELOG.md`
with the root file's contents, and no copy is left behind afterwards.

Tooling only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ngxDaE2MeedcUvki2g4wN
…ependencies

`hyperformula/package.json` is a copy of the manifest that used to sit at the
repository root, so its `repository` object still described a package living
there. npm resolves "repository" links against that object, so the package
page pointed at the repository root rather than at the directory the sources
now occupy. Added `"directory": "hyperformula"`, which is what npm reads for
a package published from a monorepo subdirectory.

Two devDependencies came along with that copy and have nothing to do with the
engine:

- `@vuepress/shared-utils` is imported by `docs/.vuepress/highlight.js`, the
  search-box plugin, and `docs/script/renderBuiltinFunctionsTable.ts` — all
  under `docs/`, which installs separately and already declares it. The split
  is the reason `docs/script/` exists at all, as `dev-docs/BUILD.md` records.
- `full-icu` has no reference anywhere: no import, no `NODE_ICU_DATA`, no
  `--icu-data-dir`.

`package-lock.json` regenerated with `npm install --package-lock-only`. The
diff is entirely the two pruned trees; the only additions are `peer`,
`optional` and `license` annotations npm re-derives for packages that were
previously reached through them. No version moved, and the lockfile still
satisfies every remaining devDependency.

Tooling only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ngxDaE2MeedcUvki2g4wN
…ine README

The directory table had a blank line and then a paragraph between the
`hyperformula-ui/` and `docs/` rows. A blank line ends a GitHub-Flavored
Markdown table, so the `docs/` row was not part of it: with no blank line
after the paragraph either, it rendered as a lazy continuation of that
sentence, pipes and all. The row that did not render is the one explaining
that `docs/` installs separately and is not a workspace member — the fact a
newcomer running `npm ci` most needs. The paragraph now follows the table.

The engine row said "its [README] is the product documentation" and linked
`dev-docs/README.md`, the index of the internal developer documentation.
It points at `hyperformula/README.md`.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ngxDaE2MeedcUvki2g4wN
Three groups, one cause: each names a path that this branch relocated.

Repository-internal prose:
- `docs/README.md:72` and `FunctionDescription.ts:8` and `:20` name
  `script/prepare-cf-assets.js` and `script/renderBuiltinFunctionsTable.ts`.
  Both generators moved to `docs/script/`; the root `script/` now holds only
  `release/` and `check-licenses.mjs`.
- `dev-docs/AGENT-TOOLING.md:34` tells an agent to read `src/` rather than
  the generated documentation when it wants to know what the code does.
  There is no `src/` at the root.

Published guide pages, which are worse because the reader cannot see the
repository to correct for it: `key-concepts.md:100`, `localizing-functions.md`
and `building.md` deep-link `blob/master/src/parser/FormulaParser.ts`,
`tree/master/src/i18n/languages` and `blob/master/test/README.md`. They still
resolve today because `master` predates the move, and become 404s on the
documentation portal the moment it lands.

`docs/guide/contributing.md:50` told contributors to add specs to the `test`
folder. `CONTRIBUTING.md` symlinks to this file and the new `README.md` sends
external contributors to it, so it is the first instruction a first-time
contributor follows, and the move never touched it.

Documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ngxDaE2MeedcUvki2g4wN
…ted to be

`hyperformula/dev-docs/TESTING.md:28` calls `npm run test` "Lint, Jest, and
the Karma browser run — the full local gate". Only the root script linted;
the engine's was `npm-run-all test:jest test:browser`. `hyperformula/AGENTS.md`
tells a contributor to run package scripts from the package directory, so the
reading that skipped lint was the likely one, and the branch it produced fails
`lint.yml` after being believed green.

The engine `test` now runs lint first. Linting stays root-owned, as
`STRUCTURE.md` records — one pass over the whole repository — so the new
`lint` script delegates upward with `npm --prefix .. run lint`, which runs the
root script with the root as its working directory. That matters: `.eslintignore`,
`.eslintrc.js` and its `tsconfigRootDir` all anchor there, so a lint started
inside `hyperformula/` would resolve none of them.

No CI job calls the engine `test` — `test.yml` runs `test:ci` and
`test:browser`, and `lint.yml` runs lint once — so this adds no CI work.

Also split the unit-testing skill's `TESTING.md` row, which promised "how to
write a case" from the engine page. That section is "How to write a test case"
on the repository-wide page; the engine page covers the suites, running them,
and per-change coverage. The row named one file for both. Each half now points
at the level that holds it, the second anchored at the section.

Tooling and documentation only; no changelog entry required.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ngxDaE2MeedcUvki2g4wN
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.32%. Comparing base (286a731) to head (b562c14).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           develop    #1757   +/-   ##
========================================
  Coverage    97.32%   97.32%           
========================================
  Files          195      195           
  Lines        15739    15739           
  Branches      3390     3390           
========================================
  Hits         15318    15318           
  Misses         421      421           
Files with missing lines Coverage Δ
hyperformula/src/AbsoluteCellRange.ts 94.59% <ø> (ø)
hyperformula/src/ArgumentSanitization.ts 100.00% <ø> (ø)
hyperformula/src/ArraySize.ts 100.00% <ø> (ø)
hyperformula/src/ArrayValue.ts 100.00% <ø> (ø)
hyperformula/src/BuildEngineFactory.ts 100.00% <ø> (ø)
hyperformula/src/Cell.ts 94.39% <ø> (ø)
hyperformula/src/CellContentParser.ts 100.00% <ø> (ø)
hyperformula/src/CellValue.ts 77.77% <ø> (ø)
hyperformula/src/ClipboardOperations.ts 96.55% <ø> (ø)
hyperformula/src/Config.ts 94.11% <ø> (ø)
... and 172 more

... and 26 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

3 participants