Coherent.js v1.0 — Wave 2b: Internal Package Merges Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Spec: docs/superpowers/specs/2026-05-17-coherent-v1-hardening-design.md — Wave 2 (Section 1 consolidation table).
Goal: Merge 6 small, narrowly-scoped packages into 3 larger consolidated ones (cli, devtools, new tooling) plus delete 2 essentially-empty packages (profiler, language-service). Takes the workspace from 22 → 16 packages.
Architecture: Each merge is one atomic commit. Sources move into subdirectories of the absorbing package (packages/devtools/src/performance/, packages/cli/src/build-tools/, etc.) preserving file boundaries. Subpath exports in the absorbing package expose the moved code at predictable paths (@coherent.js/devtools/performance/cache, @coherent.js/cli/build-tools/vite). Cross-package consumers (CLI scaffold templates, the examples/vite-integration config) get updated in the same commit as the merge.
Tech Stack: pnpm workspaces, Vitest, ESM only, Node ≥ 20. Mix of JS and TS code in the consolidated packages.
Wave 2b NOT in scope (handled later):
integrationsconsolidation (express/fastify/koa/nextjs/adapters→ subpath exports) — Wave 2c- VS Code extension absorption into
tooling/vscode-extension/— Wave 4 (paired with marketplace publish work, which has its own complexity around vsce build pipeline) - API surface lockdown, perf gates, browser tests, release — Waves 3, 4, 5
Decision: @coherent.js/vscode-extension stays as its own package for Wave 2b. The spec puts it in tooling, but its lifecycle (separate vsce build pipeline, marketplace publish, its own package.json with VS Code extension metadata) is materially different from a normal npm package merger. Folding it in here without addressing the publish flow would create real friction. Defer to Wave 4. Wave 2b ends at 16 packages; Wave 2c takes 16 → 12; if Wave 4 absorbs vscode-extension, it ends at 11 (or 12 if it goes elsewhere). Either way the spec's "~10" target is met within tolerance.
Lessons baked in from Wave 1 + Wave 2a: Every merge/delete must audit the same orphan-pattern files that Wave 2a's reviewers caught:
docs/README.md(BOTH the "Available Packages" and "Extended Packages" sections)examples/README.mdARCHITECTURE.md,DEVELOPMENT.md,PUBLISHING_GUIDE.md.changeset/pre.json.github/CODEOWNERS.github/labeler.ymltsconfig.json(project references)eslint.config.js(per-package globs and override blocks)scripts/fix-sideeffects.js(packagesToFixarray)scripts/add-exports-sections.js(package definition arrays)scripts/shared-build.mjsandscripts/build.js(hardcoded entry points)
For NEW packages (tooling), the same files need ADDITIONS (or at least verification that the new package fits the existing patterns without explicit listing).
File Structure (per merge)
After Task 1 (build-tools → cli)
packages/cli/src/build-tools/index.js(moved frompackages/build-tools/src/index.js)packages/cli/src/build-tools/vite.js(moved)packages/cli/src/build-tools/webpack.js(moved)packages/cli/src/build-tools/rollup.js(moved)packages/cli/src/build-tools/coherent-loader.js(moved)packages/cli/src/build-tools/utils.js(moved)packages/cli/package.json— adds subpath exports for./build-tools,./build-tools/vite,./build-tools/webpack,./build-tools/rollup,./build-tools/loaderpackages/cli/build.mjs— adds the new entry points to its build inputspackages/build-tools/— DELETED
After Task 2 (performance → devtools, profiler deleted)
packages/devtools/src/performance/index.js(moved frompackages/performance/src/index.js)packages/devtools/src/performance/cache.js(moved)packages/devtools/src/performance/code-splitting.js(moved)packages/devtools/src/performance/lazy-loading.js(moved)packages/devtools/package.json— adds subpath exports for./performance,./performance/cache,./performance/code-splitting,./performance/lazy-loadingpackages/devtools/build.mjs— adds new entry pointspackages/performance/— DELETEDpackages/profiler/— DELETED (was effectively empty: 138 lines of placeholder scaffolding, zero in-source consumers, devtools already has its own substantiveprofiler.js)
After Task 3 (testing + language-server → tooling, language-service deleted)
packages/tooling/package.json— NEWpackages/tooling/README.md— NEW (very brief, describes the package)packages/tooling/build.mjs— NEW (orchestrates TS compile of LSP + JS bundle of testing)packages/tooling/tsconfig.json— NEW (compilessrc/lsp/)packages/tooling/src/testing/index.js(moved frompackages/testing/src/index.js)packages/tooling/src/testing/matchers.js(moved)packages/tooling/src/testing/test-renderer.js(moved)packages/tooling/src/testing/test-utils.js(moved)packages/tooling/src/lsp/server.ts(moved frompackages/language-server/src/server.ts)packages/tooling/src/lsp/analysis/*.ts(moved)packages/tooling/src/lsp/data/*.ts(moved, plus generatedelement-attributes.generated.json)packages/tooling/src/lsp/providers/*.ts(moved)packages/tooling/scripts/extract-attributes.ts(moved frompackages/language-server/scripts/)packages/testing/— DELETEDpackages/language-server/— DELETEDpackages/language-service/— DELETED (TS-only stub, zero consumers)packages/cli/src/generators/package-scaffold.js— UPDATED: scaffold templates that emitimport ... from '@coherent.js/testing'now emit'@coherent.js/tooling/testing'packages/cli/src/commands/create.js— UPDATED:@coherent.js/testingoption label and value updated to@coherent.js/tooling/testing
After Task 4 (verification + CHANGELOG)
CHANGELOG.md— extended Unreleased section with Wave 2b subsections
Pre-flight
- Step 1: Confirm clean working tree relative to main
Run: git status
Expected: only the pre-existing modifications carried from prior sessions (package.json, several tsconfig.tsbuildinfo files, pnpm-workspace.yaml). Do not stage them.
- Step 2: Confirm baseline is green
Run: pnpm test && pnpm typecheck:packages && pnpm build
Expected: green (1672 tests passing post-Wave-2a). If anything fails on a clean main checkout, stop and investigate — Wave 2b builds on a green baseline.
Task 1: Absorb build-tools into cli
Why first: Smallest merge (6 source files, all stubs averaging ~400 bytes each). Establishes the subpath-export pattern that Task 2 and Task 3 follow. Two real consumers (examples/vite-integration, examples/ecommerce-fullstack) which must be updated in the same commit.
Files:
- Create:
packages/cli/src/build-tools/index.js,vite.js,webpack.js,rollup.js,coherent-loader.js,utils.js(copied frompackages/build-tools/src/) - Modify:
packages/cli/package.json(add subpath exports) - Modify:
packages/cli/build.mjs(add new entry points) - Modify:
examples/vite-integration/vite.config.js(update import path) - Modify:
examples/ecommerce-fullstack/package.json(replace@coherent.js/build-toolsworkspace dep with@coherent.js/cli) - Delete:
packages/build-tools/(entire directory) - Audit + modify if found:
docs/README.md,ARCHITECTURE.md,DEVELOPMENT.md,PUBLISHING_GUIDE.md,docs/packages/build-tools.md(delete or repoint),.changeset/pre.json,.github/CODEOWNERS,.github/labeler.yml,tsconfig.json,eslint.config.js,scripts/fix-sideeffects.js,scripts/add-exports-sections.js
Step 1: Pre-check — consumer map
Run from /Users/thomasdrouvin/Perso/coherent:
grep -rn "@coherent.js/build-tools" . \
--include="*.js" --include="*.ts" --include="*.json" --include="*.md" --include="*.mjs" --include="*.yml" \
2>/dev/null | grep -v "/node_modules/" | grep -v "/dist/" | grep -v "/coverage/" \
| grep -v "pnpm-lock.yaml" | grep -v "packages/build-tools/" | grep -v "CHANGELOG"Confirm the consumer list matches the Files section. New consumers (anything not listed there) → stop and add to the task scope.
Step 2: Copy source files from build-tools to cli/build-tools/
mkdir -p packages/cli/src/build-tools
cp packages/build-tools/src/index.js packages/cli/src/build-tools/index.js
cp packages/build-tools/src/vite.js packages/cli/src/build-tools/vite.js
cp packages/build-tools/src/webpack.js packages/cli/src/build-tools/webpack.js
cp packages/build-tools/src/rollup.js packages/cli/src/build-tools/rollup.js
cp packages/build-tools/src/coherent-loader.js packages/cli/src/build-tools/coherent-loader.js
cp packages/build-tools/src/utils.js packages/cli/src/build-tools/utils.jsVerify each file's content was preserved (diff -r packages/build-tools/src packages/cli/src/build-tools should show no differences).
Step 3: Adjust internal imports inside the copied files
Open each of the 6 copied files. If any file imports another file from the same directory using a relative path (e.g., vite.js imports from ./utils.js), the path doesn't need to change because the directory layout is preserved. But if any file imports a sibling using '../something' that no longer resolves, fix it.
Run:
grep -n "from ['\"]\\.\\./\\|from ['\"]\\./" packages/cli/src/build-tools/*.jsInspect each match. If they reference siblings within build-tools/, they're fine (relative path resolves correctly within the new location). If they reference anything outside build-tools/ that used to be at the package root (unlikely given file sizes), update accordingly.
Step 4: Add subpath exports to packages/cli/package.json
Open packages/cli/package.json. Find the exports field. Add the following entries (preserve existing entries):
"./build-tools": "./src/build-tools/index.js",
"./build-tools/vite": "./src/build-tools/vite.js",
"./build-tools/webpack": "./src/build-tools/webpack.js",
"./build-tools/rollup": "./src/build-tools/rollup.js",
"./build-tools/loader": "./src/build-tools/coherent-loader.js"Verify trailing-comma JSON syntax. Note: if cli's exports use a dev/prod conditional structure, match that pattern; if it uses plain string values, use plain strings.
Step 5: Update packages/cli/build.mjs
Open packages/cli/build.mjs. Find the build inputs configuration. Add entries for the 6 new files so esbuild emits dist outputs for them. Pattern follows the existing inputs.
If the current build only ships the main CLI binary and doesn't emit library JS, adding library outputs is required for the subpath exports to work in production. The exact code depends on cli's build script style — read the file first to understand the pattern.
Step 6: Update examples/vite-integration/vite.config.js
Change:
import { coherentVitePlugin } from '@coherent.js/build-tools/vite';to:
import { coherentVitePlugin } from '@coherent.js/cli/build-tools/vite';Step 7: Update examples/ecommerce-fullstack/package.json
Replace:
"@coherent.js/build-tools": "workspace:*",with:
"@coherent.js/cli": "workspace:*",(or, if @coherent.js/cli is already in the dependencies list, just delete the build-tools line — don't duplicate). Verify final JSON validity.
Step 8: Delete the build-tools package
git rm -r packages/build-toolsStep 9: Audit Task-1-pattern files
For each file in this list, grep for build-tools and remove the matching entry (if any):
for f in \
docs/README.md ARCHITECTURE.md DEVELOPMENT.md PUBLISHING_GUIDE.md \
.changeset/pre.json .github/CODEOWNERS .github/labeler.yml \
tsconfig.json eslint.config.js \
scripts/fix-sideeffects.js scripts/add-exports-sections.js \
scripts/shared-build.mjs scripts/build.js; do
echo "=== $f ==="
grep -n "build-tools" "$f" 2>/dev/null || echo "(no matches)"
doneFor each file with matches:
docs/README.md— likely 1-2 references. Delete or update them to point at@coherent.js/cli/build-toolspaths. If updating, do not invent docs sections — only fix what's there.ARCHITECTURE.md,DEVELOPMENT.md— delete the build-tools tree entriesPUBLISHING_GUIDE.md— delete the build-tools list item and renumber subsequent items.changeset/pre.json— delete the build-tools entry.github/CODEOWNERS— deletepackages/build-tools/line if present.github/labeler.yml— deletepackages/build-tools/**/*line if presenttsconfig.json— delete the{ "path": "./packages/build-tools" }reference if presenteslint.config.js— delete any build-tools-specific glob or override block if presentscripts/fix-sideeffects.js— remove'build-tools'frompackagesToFixarray if presentscripts/add-exports-sections.js— remove the build-tools section entries (around lines 169 and 171 in the pre-Wave-2b numbering). Runnode --check scripts/add-exports-sections.jsafter editing.scripts/shared-build.mjsandscripts/build.js— unlikely to have a build-tools entry point hardcoded, but grep just in case.
Also delete docs/packages/build-tools.md — it's a standalone doc for the now-deleted package:
git rm docs/packages/build-tools.md(Or repoint it to the new @coherent.js/cli/build-tools namespace if the content has substantive material; honestly given it's a thin reference doc, deletion + a follow-up Wave 5 docs note is cleaner. Default: delete.)
Step 10: Regenerate the lockfile
pnpm installVerify grep "@coherent.js/build-tools" pnpm-lock.yaml returns nothing.
Step 11: Full quality gate
pnpm test
pnpm typecheck:packages
pnpm buildExpected: all green. The build-tools package's small test suite (if any) goes away with the deletion. New cli subpath exports become available for consumption.
Verify the moved code is actually accessible:
node -e "import('@coherent.js/cli/build-tools/vite').then(m => console.log('vite OK', Object.keys(m)))"(Run from repo root with the workspace activated. If it fails with "Cannot find module", the exports field or build pipeline didn't pick up the new paths.)
Step 12: Commit
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git add -A packages docs ARCHITECTURE.md DEVELOPMENT.md PUBLISHING_GUIDE.md .changeset .github tsconfig.json eslint.config.js scripts examples pnpm-lock.yaml
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git commit -m "$(cat <<'EOF'
refactor(cli): absorb @coherent.js/build-tools into @coherent.js/cli
Moves vite/webpack/rollup/loader plugins from the standalone
build-tools package into packages/cli/src/build-tools/, exposed via
subpath exports (@coherent.js/cli/build-tools/vite etc.). The build
plugins are dev-time CLI tooling, not runtime, so the cli package is
their natural home.
Examples updated: vite-integration uses the new import path,
ecommerce-fullstack swaps its workspace dep.
Part of Wave 2b (internal merges) for v1.0 stable hardening.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"(The PNPM_CONFIG_* prefix is the established workaround for the pnpm-verify-on-script-run issue. If the hook runs cleanly without it, that's fine too.)
Task 2: Absorb performance into devtools, delete profiler
Why second: Now that the subpath-export pattern is established (Task 1), the same pattern applies to performance. profiler deserves outright deletion (138 lines of placeholder scaffolding, zero in-source consumers, devtools already has substantive profiler.js for the same concern).
Files:
- Create:
packages/devtools/src/performance/index.js,cache.js,code-splitting.js,lazy-loading.js(moved frompackages/performance/src/) - Modify:
packages/devtools/package.json(add subpath exports) - Modify:
packages/devtools/build.mjs(add new entry points) - Delete:
packages/performance/ - Delete:
packages/profiler/(no merge, just delete) - Audit + modify if found: same orphan-pattern file list as Task 1
Step 1: Pre-check — consumer maps for both packages
echo "=== performance consumers ===" && grep -rn "@coherent.js/performance\b" . \
--include="*.js" --include="*.ts" --include="*.json" --include="*.md" --include="*.mjs" --include="*.yml" \
2>/dev/null | grep -v "/node_modules/" | grep -v "/dist/" | grep -v "/coverage/" \
| grep -v "pnpm-lock.yaml" | grep -v "packages/performance/" | grep -v "CHANGELOG"
echo "=== profiler consumers ===" && grep -rn "@coherent.js/profiler" . \
--include="*.js" --include="*.ts" --include="*.json" --include="*.md" --include="*.mjs" --include="*.yml" \
2>/dev/null | grep -v "/node_modules/" | grep -v "/dist/" | grep -v "/coverage/" \
| grep -v "pnpm-lock.yaml" | grep -v "packages/profiler/" | grep -v "CHANGELOG"Expected (from pre-execution audit): NEITHER package has live JS/TS consumers. All matches should be docs/scripts/changeset references. If a real consumer turns up, stop and report.
Step 2: Copy performance source files
mkdir -p packages/devtools/src/performance
cp packages/performance/src/index.js packages/devtools/src/performance/index.js
cp packages/performance/src/cache.js packages/devtools/src/performance/cache.js
cp packages/performance/src/code-splitting.js packages/devtools/src/performance/code-splitting.js
cp packages/performance/src/lazy-loading.js packages/devtools/src/performance/lazy-loading.jsVerify content preserved (diff -r packages/performance/src packages/devtools/src/performance).
Step 3: Adjust internal imports in copied performance files
Same as Task 1 Step 3 — grep for relative imports, verify they still resolve in the new location. The performance package was self-contained (no cross-package imports for its internal files), so relative-within-directory imports should be fine.
Step 4: Add subpath exports to packages/devtools/package.json
Open packages/devtools/package.json. Find the exports field. Add:
"./performance": "./src/performance/index.js",
"./performance/cache": "./src/performance/cache.js",
"./performance/code-splitting": "./src/performance/code-splitting.js",
"./performance/lazy-loading": "./src/performance/lazy-loading.js"Match the existing entries' format (dev/prod conditional or plain string).
Step 5: Update packages/devtools/build.mjs
Add the 4 new files to the build inputs (matching the package's existing pattern).
Step 6: Delete the performance package
git rm -r packages/performanceStep 7: Delete the profiler package outright
git rm -r packages/profilerNo merge — profiler was 138 lines of placeholder scaffolding with zero in-source consumers. packages/devtools/src/profiler.js (the substantive profiling code) is unaffected and remains the real implementation.
Step 8: Audit Task-1-pattern files
Run for BOTH performance and profiler:
for pkg in performance profiler; do
echo "=== $pkg ==="
for f in \
docs/README.md ARCHITECTURE.md DEVELOPMENT.md PUBLISHING_GUIDE.md \
.changeset/pre.json .github/CODEOWNERS .github/labeler.yml \
tsconfig.json eslint.config.js \
scripts/fix-sideeffects.js scripts/add-exports-sections.js \
scripts/shared-build.mjs scripts/build.js; do
if grep -q "$pkg" "$f" 2>/dev/null; then
echo "$f has matches"
grep -n "$pkg" "$f"
fi
done
doneFor each match, remove or update following the same patterns as Task 1.
Also delete or update these docs files if they exist:
docs/packages/performance.md— delete or repoint to@coherent.js/devtools/performancedocs/packages/profiler.md— delete (no replacement)
Step 9: Regenerate the lockfile
pnpm installVerify both grep "@coherent.js/performance" pnpm-lock.yaml and grep "@coherent.js/profiler" pnpm-lock.yaml return nothing.
Step 10: Full quality gate
pnpm test
pnpm typecheck:packages
pnpm buildExpected: green. The performance package's test suite (if any) goes away; profiler's tests (if any) go away. Devtools gains new subpath exports.
Verify accessibility:
node -e "import('@coherent.js/devtools/performance/cache').then(m => console.log('cache OK', Object.keys(m).slice(0,5)))"Step 11: Commit
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git add -A packages docs ARCHITECTURE.md DEVELOPMENT.md PUBLISHING_GUIDE.md .changeset .github tsconfig.json eslint.config.js scripts pnpm-lock.yaml
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git commit -m "$(cat <<'EOF'
refactor(devtools): absorb @coherent.js/performance, drop @coherent.js/profiler
Moves performance utilities (cache, code-splitting, lazy-loading) into
packages/devtools/src/performance/ exposed via subpath exports
(@coherent.js/devtools/performance/cache etc.). Devtools already
contained its own profiler.js and performance-dashboard.js for the
same domain; the three-package split was artificial.
@coherent.js/profiler is deleted outright — it was 138 lines of
placeholder scaffolding with zero in-source consumers.
Part of Wave 2b (internal merges) for v1.0 stable hardening.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"Task 3: Create tooling package, absorb testing + language-server, delete language-service
Why third: Largest merge. Creates a new package (tooling) with a mixed JS+TS build, absorbs two real packages (testing, language-server), and deletes one TS-only stub (language-service). Cross-package consumer updates required for cli's scaffold templates that emit import ... from '@coherent.js/testing'.
@coherent.js/vscode-extension is NOT touched in this task. It stays as its own package; deferred to Wave 4 (paired with marketplace publish work).
Files:
- Create: entire
packages/tooling/tree (see File Structure section above for full layout) - Modify:
packages/cli/src/generators/package-scaffold.js(update template strings:'@coherent.js/testing'→'@coherent.js/tooling/testing') - Modify:
packages/cli/src/commands/create.js(update the CLI option label and value around line 200) - Delete:
packages/testing/ - Delete:
packages/language-server/ - Delete:
packages/language-service/ - Audit + modify if found: same orphan-pattern file list as Task 1, for all THREE deleted packages
- Add (NEW package): the same files need ENTRIES for
tooling:.github/CODEOWNERS— addpackages/tooling/ @Tomdrouv1if pattern is followed.changeset/pre.json— leave alone (only matters for prerelease-tracking older versions; the new tooling package starts at 1.0.0 directly)tsconfig.json— add{ "path": "./packages/tooling" }to referenceseslint.config.js— verify it coverspackages/tooling/**/*via existing globs; if not, addscripts/add-exports-sections.js— add a tooling entry if the pattern requires it (optional; consider whether the orphan script is worth maintaining at all)
Step 1: Pre-check — consumer maps for all three packages
for pkg in testing language-server language-service; do
echo "=== $pkg consumers ==="
grep -rn "@coherent.js/$pkg\b" . \
--include="*.js" --include="*.ts" --include="*.json" --include="*.md" --include="*.mjs" --include="*.yml" \
2>/dev/null | grep -v "/node_modules/" | grep -v "/dist/" | grep -v "/coverage/" \
| grep -v "pnpm-lock.yaml" | grep -v "packages/$pkg/" | grep -v "CHANGELOG"
doneExpected:
testing: matches inpackages/cli/src/commands/create.js,packages/cli/src/generators/package-scaffold.js, plus docs/scripts/changesetlanguage-server: zero in-source consumers (LSP is consumed by editors via the binary)language-service: zero in-source consumers
If anything else turns up (an example uses @coherent.js/testing, etc.), add to scope.
Step 2: Create the tooling package skeleton
mkdir -p packages/tooling/src/testing
mkdir -p packages/tooling/src/lsp
mkdir -p packages/tooling/scripts
mkdir -p packages/tooling/binCreate packages/tooling/package.json:
{
"name": "@coherent.js/tooling",
"version": "1.0.0-beta.8",
"description": "Coherent.js dev-time tooling: testing utilities (Vitest matchers, render harness) and Language Server Protocol implementation.",
"type": "module",
"main": "./dist/testing/index.js",
"exports": {
"./testing": "./dist/testing/index.js",
"./testing/renderer": "./dist/testing/test-renderer.js",
"./testing/utils": "./dist/testing/test-utils.js",
"./testing/matchers": "./dist/testing/matchers.js",
"./lsp": "./dist/lsp/server.js"
},
"bin": {
"coherent-language-server": "./dist/lsp/server.js"
},
"files": [
"dist/",
"bin/",
"README.md",
"LICENSE"
],
"scripts": {
"build": "node build.mjs",
"clean": "rm -rf dist/",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"engines": { "node": ">=20.0.0" },
"license": "MIT",
"peerDependencies": {
"@coherent.js/core": "workspace:*"
},
"dependencies": {
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.11"
},
"devDependencies": {
"vitest": "workspace:*",
"tsx": "^4.0.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"sideEffects": false
}(Adjust dependencies to match what packages/language-server/package.json actually declared — read it first to get the exact list and versions.)
Create packages/tooling/README.md:
# @coherent.js/tooling
Dev-time tooling for Coherent.js: testing utilities and Language Server Protocol implementation.
## Subpath exports
- `@coherent.js/tooling/testing` — Vitest matchers, render harness, test utilities
- `@coherent.js/tooling/testing/renderer` — server-side render harness for component tests
- `@coherent.js/tooling/testing/matchers` — Vitest custom matchers (`toRender`, `toMatchSnapshot`, etc.)
- `@coherent.js/tooling/testing/utils` — assorted test helpers
- `@coherent.js/tooling/lsp` — Language Server Protocol implementation (consumed via the `coherent-language-server` binary)
## Binary
Installs `coherent-language-server`. Configure your editor's LSP client to launch this binary with `--stdio`.
## License
MITCreate packages/tooling/LICENSE — copy from packages/testing/LICENSE or the repo-root LICENSE.
Create packages/tooling/tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist/lsp",
"rootDir": "./src/lsp",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"resolveJsonModule": true
},
"include": ["src/lsp/**/*.ts"],
"exclude": ["dist", "node_modules"]
}(Match the patterns from packages/language-server/tsconfig.json — read it first.)
Create packages/tooling/build.mjs. Pattern requirements (prose; do NOT copy code blindly — adapt from existing references):
The build must produce two output trees:
dist/lsp/— TypeScript compilation output. Source:src/lsp/**/*.ts. Pipeline matchespackages/language-server/package.json's pre-Wave-2b build script: first runtsx scripts/extract-attributes.ts(generatessrc/lsp/data/element-attributes.generated.json), thentsc(compiles TS), then copy the generated JSON intodist/lsp/data/.dist/testing/— JavaScript bundle output. Source:src/testing/*.js. Pipeline matchespackages/testing/build.mjs's pre-Wave-2b approach (likely esbuild with per-file entry points, format esm, platform node, target node20, bundle false).
References to read before writing:
packages/language-server/package.json(build script line)packages/language-server/tsconfig.json(compiler options)packages/testing/build.mjs(the existing JS build pattern)
Implementation notes:
- Use
execFileSyncfromnode:child_process(NOTexecSync— execFileSync passes args as an array which avoids shell injection). Example shape:execFileSync('tsx', ['scripts/extract-attributes.ts'], { stdio: 'inherit' })andexecFileSync('tsc', [], { stdio: 'inherit' }). - Use
cpSyncandmkdirSyncfromnode:fsfor any file copies;rmSync({ recursive: true, force: true })for clean. - For esbuild,
import { build } from 'esbuild'and call it with an options object matching the existing testing/build.mjs pattern.
The exact code is left to the implementer — the requirement is "two-phase build producing the two dist subtrees that match the package.json exports field paths." Verify by listing dist/ after build and confirming both dist/lsp/server.js and dist/testing/index.js exist.
Step 3: Move testing source files into tooling
cp packages/testing/src/index.js packages/tooling/src/testing/index.js
cp packages/testing/src/matchers.js packages/tooling/src/testing/matchers.js
cp packages/testing/src/test-renderer.js packages/tooling/src/testing/test-renderer.js
cp packages/testing/src/test-utils.js packages/tooling/src/testing/test-utils.jsAlso copy the test directory if it exists and has substantive tests:
if [ -d packages/testing/test ]; then
mkdir -p packages/tooling/test/testing
cp -r packages/testing/test/* packages/tooling/test/testing/
fiAdjust imports inside the moved testing tests so they reference ../../src/testing/... instead of ../src/....
Step 4: Move language-server source files into tooling
cp -r packages/language-server/src/* packages/tooling/src/lsp/
cp -r packages/language-server/scripts/* packages/tooling/scripts/Inside packages/tooling/scripts/extract-attributes.ts, find any path references like '../src/data/' and adjust to '../src/lsp/data/' (because the script now sits at packages/tooling/scripts/ and the LSP code is at packages/tooling/src/lsp/, not at packages/tooling/src/).
Read the moved src/lsp/server.ts and its analysis/ and providers/ subdirectories. Adjust any relative imports that broke due to the new directory depth (most should still work — the lsp/ subdirectory preserves the language-server's internal structure).
Step 5: Add the LSP binary shim
Create packages/tooling/bin/coherent-language-server (or omit if dist/lsp/server.js already has a #!/usr/bin/env node shebang line and is executable). Recommended: rely on the shebang and the bin field in package.json pointing at ./dist/lsp/server.js.
If the original packages/language-server/dist/server.js had a shebang, the moved TS source should preserve it (TS doesn't add a shebang automatically; check packages/language-server/src/server.ts and replicate at the top of packages/tooling/src/lsp/server.ts).
Step 6: Update CLI scaffold templates
Open packages/cli/src/generators/package-scaffold.js. Find every string literal containing @coherent.js/testing (around lines 435, 439, 483, 503). Update each to @coherent.js/tooling/testing.
Open packages/cli/src/commands/create.js. Around line 200, find:
{ title: '@coherent.js/testing', value: 'testing', description: 'Testing utilities & helpers' }Update to:
{ title: '@coherent.js/tooling (testing)', value: 'testing', description: 'Testing utilities & helpers (subpath of tooling)' }Keep the value: 'testing' if scaffold logic switches on that string — changing the value would require following the call chain. Only change the displayed title and description.
Step 7: Delete the three old packages
git rm -r packages/testing
git rm -r packages/language-server
git rm -r packages/language-serviceStep 8: Audit Task-1-pattern files for all three deleted packages AND for the new tooling package
For deletions (testing, language-server, language-service):
for pkg in testing language-server language-service; do
echo "=== removing $pkg references ==="
for f in \
docs/README.md ARCHITECTURE.md DEVELOPMENT.md PUBLISHING_GUIDE.md \
.changeset/pre.json .github/CODEOWNERS .github/labeler.yml \
tsconfig.json eslint.config.js \
scripts/fix-sideeffects.js scripts/add-exports-sections.js \
scripts/shared-build.mjs scripts/build.js; do
if grep -q "$pkg" "$f" 2>/dev/null; then
echo "$f has matches"
grep -n "$pkg" "$f"
fi
done
doneRemove matching entries.
Delete docs/packages/testing.md, docs/packages/language-server.md, docs/packages/language-service.md if they exist.
For NEW tooling package, ADD entries to:
.github/CODEOWNERS—packages/tooling/ @Tomdrouv1.github/labeler.yml—packages/tooling/**/*under the appropriate label (look at how other packages are categorized; tooling fits with build/dev tools)tsconfig.json— add{ "path": "./packages/tooling" }to the references arrayeslint.config.js— verify the file is covered by the existing globs. Ifpackages/toolingis not matched, add it (likely fits the same category astestingdid previously).
Do NOT add tooling to scripts/add-exports-sections.js — that script is a docs generator flagged for removal in a future wave; adding entries to it is wasted effort.
Step 9: Update top-level docs to reflect the consolidation
docs/README.md— under "Available Packages" or equivalent, replace the testing/language-server/language-service entries with a singletoolingentry. Same for "Extended Packages" section if applicable.ARCHITECTURE.md— update the package tree: remove the 3 deleted packages, add atooling/entry.DEVELOPMENT.md— same as ARCHITECTURE.md.PUBLISHING_GUIDE.md— replace the 3 deleted entries with onetoolingentry. Renumber the list.
Step 10: Regenerate the lockfile
pnpm installVerify:
grep -E "@coherent.js/(testing|language-server|language-service)\b" pnpm-lock.yamlShould return nothing (or only historical hashes that get cleaned on subsequent installs).
Verify the new package is registered:
pnpm list --recursive --depth -1 | grep toolingShould show @coherent.js/tooling.
Step 11: Full quality gate
pnpm --filter @coherent.js/tooling run build
pnpm --filter @coherent.js/tooling run test
pnpm --filter @coherent.js/cli run test
pnpm test
pnpm typecheck:packages
pnpm buildExpected: all green. The tooling package builds (both LSP and testing artifacts present in dist/), all tests pass, CLI tests pass (scaffold templates now emit new tooling paths).
Verify subpath imports work:
node -e "import('@coherent.js/tooling/testing').then(m => console.log('testing OK:', Object.keys(m).slice(0, 5)))"Verify the binary is installed:
ls node_modules/.bin/coherent-language-serverShould print the path (it's a symlink into the tooling package's bin).
Step 12: Commit
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git add -A packages docs ARCHITECTURE.md DEVELOPMENT.md PUBLISHING_GUIDE.md .changeset .github tsconfig.json eslint.config.js scripts pnpm-lock.yaml
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git commit -m "$(cat <<'EOF'
refactor(tooling): create @coherent.js/tooling, absorb testing + language-server, drop language-service
New @coherent.js/tooling package consolidates dev-time tooling:
- @coherent.js/tooling/testing (and subpaths): Vitest matchers,
render harness, test utilities — moved from @coherent.js/testing
- @coherent.js/tooling/lsp + coherent-language-server binary:
Language Server Protocol implementation — moved from
@coherent.js/language-server
@coherent.js/language-service (TS-only stub, zero consumers) is
deleted with no replacement.
@coherent.js/vscode-extension is NOT touched in this commit — it
stays as its own package pending Wave 4 (marketplace publish work).
CLI scaffold templates updated to emit '@coherent.js/tooling/testing'
imports instead of '@coherent.js/testing'.
Part of Wave 2b (internal merges) for v1.0 stable hardening.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"Task 4: Wave 2b verification + CHANGELOG entry
Files:
- Modify:
CHANGELOG.md
Step 1: Full pre-flight quality gate including CLEAN BUILD
pnpm clean
pnpm install
pnpm build
pnpm test
pnpm lint
pnpm typecheck
pnpm typecheck:packagesExpected: all 7 commands green. The clean+build pair catches stale dist artifacts that could mask issues from the package moves.
If anything fails, report BLOCKED. Do NOT edit code in this task to fix regressions.
Step 2: Confirm workspace shrank to 16 packages
ls -1 packages/ | wc -lExpected: 16 (down from 22 at start of Wave 2b: -6 packages = -build-tools -performance -profiler -testing -language-server -language-service +tooling).
Per-package verification:
for pkg in build-tools performance profiler testing language-server language-service; do
if [ -d "packages/$pkg" ]; then
echo "FAIL: $pkg still exists"
else
echo "OK: $pkg gone"
fi
done
if [ -d "packages/tooling" ]; then
echo "OK: tooling exists"
else
echo "FAIL: tooling missing"
fiAll 6 should print OK and tooling should exist.
Step 3: Extend the ## [Unreleased] section of CHANGELOG.md
Open CHANGELOG.md. The existing ## [Unreleased] section contains Wave 1 + Wave 2a entries. Add NEW Wave 2b subsections AFTER the existing Wave 2a ones (and BEFORE ## [1.0.0-beta.8]):
### Removed (Wave 2b)
- **BREAKING:** Removed standalone `@coherent.js/build-tools` package. Its plugins (vite, webpack, rollup, loader) now ship as subpath exports of `@coherent.js/cli`. Migration: replace `import ... from '@coherent.js/build-tools/vite'` with `import ... from '@coherent.js/cli/build-tools/vite'`. The `@coherent.js/build-tools` package name is no longer published.
- **BREAKING:** Removed standalone `@coherent.js/performance` package. Its utilities (cache, code-splitting, lazy-loading) now ship as subpath exports of `@coherent.js/devtools`. Migration: replace `import ... from '@coherent.js/performance/cache'` with `import ... from '@coherent.js/devtools/performance/cache'`.
- **BREAKING:** Removed `@coherent.js/profiler` package. It contained 138 lines of placeholder scaffolding with no in-source consumers. `@coherent.js/devtools` already provides the substantive profiling code via its own `profiler.js`.
- **BREAKING:** Removed standalone `@coherent.js/testing` package. Its Vitest matchers, render harness, and test utilities now ship as subpath exports of `@coherent.js/tooling`. Migration: replace `import ... from '@coherent.js/testing'` with `import ... from '@coherent.js/tooling/testing'`.
- **BREAKING:** Removed standalone `@coherent.js/language-server` package. Its Language Server Protocol implementation and `coherent-language-server` binary now ship inside `@coherent.js/tooling`. Editor LSP configs that launched the binary by package-prefixed path should now reference `@coherent.js/tooling` (or just continue to invoke the `coherent-language-server` binary if it's on PATH).
- **BREAKING:** Removed `@coherent.js/language-service` package. It was a TypeScript-only stub with no consumers; deleted with no replacement.
### Added (Wave 2b)
- **NEW:** `@coherent.js/tooling` package consolidates dev-time tooling. Subpaths: `./testing`, `./testing/renderer`, `./testing/utils`, `./testing/matchers`, `./lsp`. Bin: `coherent-language-server`. `@coherent.js/vscode-extension` remains a separate package for now — pending Wave 4 (marketplace publish work).
- `@coherent.js/cli` now exposes `./build-tools`, `./build-tools/vite`, `./build-tools/webpack`, `./build-tools/rollup`, `./build-tools/loader` subpaths (absorbed from the deleted `@coherent.js/build-tools` package).
- `@coherent.js/devtools` now exposes `./performance`, `./performance/cache`, `./performance/code-splitting`, `./performance/lazy-loading` subpaths (absorbed from the deleted `@coherent.js/performance` package).
### Changed (Wave 2b)
- `packages/cli/src/generators/package-scaffold.js` and `packages/cli/src/commands/create.js` updated to emit/display the new `@coherent.js/tooling/testing` import path when scaffolding new projects.
- `examples/vite-integration/vite.config.js` updated to import from `@coherent.js/cli/build-tools/vite`.
- `examples/ecommerce-fullstack/package.json` swapped its `@coherent.js/build-tools` workspace dep for `@coherent.js/cli`.
### Notes (Wave 2b)
- Workspace shrank from 22 → 16 packages.
- Net file move: ~30 source files relocated into 3 absorbing packages, 5 small/empty packages deleted, 1 new `tooling` package created with a mixed JS+TS build.
- Remaining 16 packages: adapters, api, cli, client, core, database, devtools, express, fastify, forms, i18n, koa, nextjs, seo, state, tooling, vscode-extension. (vscode-extension stays separate pending Wave 4; Wave 2c will consolidate adapters+express+fastify+koa+nextjs into `integrations` to reach the spec target.)Step 4: Commit
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git add CHANGELOG.md
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false CI=true git commit -m "$(cat <<'EOF'
docs(changelog): record Wave 2b internal merges
Documents the absorption of build-tools into cli, performance into
devtools (profiler outright deleted), and the creation of the new
tooling package absorbing testing + language-server (language-service
deleted).
Closes Wave 2b of v1.0 stable hardening. Trunk is green; workspace is
now 16 packages (was 22). Ready for Wave 2c (integrations
consolidation: express+fastify+koa+nextjs+adapters → integrations
subpaths).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"Step 5: Final confirmation
git log --oneline <wave-2b-base-sha>..HEADExpected (most recent first): your CHANGELOG commit, then Task 3 commit, Task 2 commit, Task 1 commit. Four commits total (plus follow-up fix-up commits if any).
git status --shortExpected: only pre-existing unrelated drift. Nothing from Wave 2b uncommitted.
Post-Wave-2b handoff
Wave 2b is done. Workspace is now 16 packages.
Next plans:
- Wave 2c —
integrationsconsolidation: New@coherent.js/integrationspackage with subpath exports forexpress,fastify,koa,nextjs, and the three adapters (astro/remix/sveltekit currently bundled in@coherent.js/adapters). Biggest restructure of the wave — many downstream consumers. Plans this after Wave 2b merges to reflect post-merge workspace state. Target: 16 → 12 packages. - Wave 3 — Lockdown (API surface snapshots, perf CI gates).
- Wave 4 — Browser parity (HMR dev server, Playwright E2E, VS Code marketplace publish — this is when vscode-extension absorbs into tooling).
- Wave 5 — Release (migration guide, rc soak, 1.0 tag).