Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

<!-- nx configuration start-->
<!-- Leave the start & end comments to automatically receive updates. -->

Expand All @@ -21,3 +25,89 @@
- The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax

<!-- nx configuration end-->

# Repository overview

Nx monorepo (yarn 4, berry) that publishes the `json-difference` library — a path-based JSON delta computer — plus ancillary projects that consume it.

| Project | Path | Type | Notes |
|---|---|---|---|
| `json-difference` | `libs/json-difference` | library (published) | Core lib, ESM/CJS + standalone browser bundle |
| `json-difference-cli` | `libs/json-difference-cli` | library (published) | `jd` CLI wrapper (yargs) |
| `mcp-json-diff` | `tools/mcp-json-diff` | application | MCP server exposing the lib via stdio |
| `playground` | `apps/playground` | application | React + Vite demo deployed to GitHub Pages |
| `example` | `apps/example` | application | Runnable Node usage scripts (`simple.ts`, `stress.ts`) |

Node >=18.17. Package manager is yarn — **do not** use npm/pnpm here (releases use `yarn dlx nx`).

# Common commands

Prefer `yarn nx` over project-local tooling. Key targets:

```sh
yarn nx test json-difference # Jest, ts-jest, finds **/*.spec.ts
yarn nx test json-difference --testFile=get-diff.spec.ts # single spec
yarn nx build json-difference # vite build (ESM/CJS via vite.config.mts)
yarn nx run json-difference:build-browser # standalone browser bundle (vite.config.browser.mts)
yarn nx run json-difference:build-to-s3 # build-browser + prepare-to-s3.ts (versioned bundle)
yarn nx lint json-difference # ESLint (maxWarnings: 0)
yarn nx run-many --target=type-check # tsc --noEmit across all projects

yarn nx serve playground # React dev server
yarn nx run example:test # runs simple.ts + stress.ts via tsx
yarn nx run mcp-json-diff:serve # run MCP server directly (stdio)
yarn nx run mcp-json-diff:test-browser # open @modelcontextprotocol/inspector UI
yarn nx run json-difference-cli:install-local # build CLI and `npm install -g` it

yarn graph # full dep graph; yarn graph:affected for subset
yarn release[:dry-run] # per-project semver (do not run casually)
```

The `claude` script (`yarn claude`) builds the MCP server then launches Claude Code with it available — `.mcp.json` points at `tools/mcp-json-diff/bin/src/index.js`, so the MCP server must be built before it will respond.

# Core library architecture

The library is a 4-function pipeline. Understand `getStructPaths` first — everything else operates on its output.

1. **`getStructPaths(struct, isLodashLike)`** flattens any JSON into a `Record<path, leafValue>`. Non-leaf nodes get sentinel values: `"@{}"` for objects, `"@[]"` for arrays. The synthetic `__root__` key records the top-level container type. `__start__` is an internal recursion sentinel — never leaks to output.
2. **`getPathsDiff(A, B)`** returns `[path, value]` tuples present in A but missing in B. `getDiff` calls it twice (A−B → `removed`, B−A → `added`).
3. **`getEditedPaths(oldPaths, newPaths)`** returns `[path, oldValue, newValue]` for paths present in both with changed values.
4. **`sanitizeDelta`** (`helpers/sanitize-delta.ts`) strips/rewrites the `@{}` / `@[]` sentinels so they never appear in user-visible output. Any new internal sentinel must be handled here or it will leak.

`getDiff` is the thin public composition of these four. JSON-string inputs are `JSON.parse`-ed up front, so the core routines always see real objects/arrays.

**Path formats** are mutually exclusive modes set by `isLodashLike`:
- default (slash): `a/b/0[]` — array indices marked by trailing `[]`
- lodash: `a.b[0]` — standard bracket/dot notation

Any change to path formatting must be made in `generatePath` (`get-struct-paths.ts`) and the corresponding spec updated; path shape is part of the public API and is locked down by spec fixtures.

The README's "Reference table of operations" is the behavioral contract — if a change alters any row in that table, it's a breaking change.

# Consumer projects

- **CLI** (`libs/json-difference-cli`) — thin yargs wrapper calling `getDiff`. Built with `@nx/js:tsc` to `bin/`; published as `json-difference-cli`.
- **MCP server** (`tools/mcp-json-diff`) — exposes `get_diff`, `get_edited_paths`, `get_struct_paths`, `get_paths_diff` as MCP tools plus prompt templates (breaking-change summary, changelog, config drift, migration guide). Uses `StdioServerTransport`; all inputs accept object/array/JSON-string via `parseJson`. Not published to npm.
- **Playground** (`apps/playground`) — React 19 + Chakra UI + Monaco, Vite-built, deployed to GitHub Pages by `cd.yml`. Single-file app in `src/App.tsx`.
- **Example** (`apps/example`) — `simple.ts` is the doc-style demo; `stress.ts` exercises large payloads.

# Module boundaries

`eslint.config.mjs` enforces Nx tag-based deps:

- `scope:shared` (lib, cli, mcp) → may depend only on `scope:shared`
- `scope:playground` → `scope:shared | scope:playground`
- `scope:example` → `scope:shared | scope:example`
- `type:feature` (= the core lib) → `type:feature` only

Changing a project's tags in `project.json` changes who can import it — update boundaries deliberately.

# Release flow

Each publishable lib versions independently via `@jscutlery/semver` → `ngx-deploy-npm` → GitHub release, driven by **conventional commits**. `nx.json`'s `release.version.preVersionCommand` runs `nx run-many -t build` first. Do not hand-edit versions; do not amend published commits. CI workflows live in `.github/workflows/` (`ci.yml`, `cd.yml`, `release-and-publish.yml`).

# Conventions

- Commits: Conventional Commits (required by the release pipeline).
- Prettier: `endOfLine: 'auto'` is enforced via ESLint — running prettier with default config locally will flag CRLF/LF noise. Let ESLint --fix handle it.
- `AGENTS.md` and `CLAUDE.md` share the auto-updated Nx block between the `nx configuration` comment markers; keep those markers intact or the nx plugin will stop updating them.
8 changes: 5 additions & 3 deletions apps/playground/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "apps/playground/dist"
"outputPath": "{projectRoot}/dist"
}
},
"serve": {
"executor": "@nx/vite:dev-server",
"defaultConfiguration": "development"
"options": {
"buildTarget": "playground:build"
}
},
"preview": {
"executor": "@nx/vite:preview-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "playground:build"
}
Expand Down
18 changes: 12 additions & 6 deletions apps/playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState, useMemo } from 'react'
import { Box, Heading, Container, Text, Button, Stack, Grid, GridItem, Textarea } from '@chakra-ui/react'
import Editor from 'react-monaco-editor'
import { getDiff } from 'json-difference'
import { version as jsonDifferenceVersion } from 'json-difference/package.json'
import { Trash2, Plus } from 'lucide-react'

const oldJsonExample = `{ "foo": { "bar": "true" } }`
Expand Down Expand Up @@ -43,13 +44,18 @@ const App = () => {
<>
<Container maxW={'12xl'}>
<Stack as={Box} gap={{ base: 8, md: 12 }} py={{ base: 20, md: 4 }}>
<Heading fontWeight={600} textAlign={'center'} fontSize={{ base: '2xl', sm: '4xl', md: '4xl' }} lineHeight={'110%'}>
Make Diff with Playground
<br />
<Text as={'span'} color={'green.400'}>
JSON Difference
<Box textAlign={'center'}>
<Heading fontWeight={600} fontSize={{ base: '2xl', sm: '4xl', md: '4xl' }} lineHeight={'110%'}>
Make Diff with Playground
<br />
<Text as={'span'} color={'green.400'}>
JSON Difference
</Text>
</Heading>
<Text mt={2} fontSize={'sm'} color={'gray.500'}>
v{jsonDifferenceVersion}
</Text>
</Heading>
</Box>
<Grid templateColumns="repeat(2, 1fr)" gap="1">
<GridItem w="100%">
<Text>Original JSON</Text>
Expand Down
15 changes: 13 additions & 2 deletions apps/playground/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { defineConfig } from 'vite'
import { defineConfig, searchForWorkspaceRoot } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
root: import.meta.dirname,
cacheDir: '../../node_modules/.vite/playground',
plugins: [react()],
resolve: { tsconfigPaths: true },
base: '/json-difference'
base: '/json-difference',
server: {
host: 'localhost',
port: 3000,
fs: {
allow: [searchForWorkspaceRoot(import.meta.dirname)]
}
},
preview: {
host: 'localhost',
port: 3000
}
})
Loading