Standalone package for serving and generating document git history, with dual modes: REST API server for local development and CLI batch generator for CI builds.

## Overview

The Doc History Server is a sub-package at `packages/doc-history-server/` that handles all git history extraction for the [Document History](https://zudo-doc.takazudomodular.com/docs/guides/doc-history) feature. It was extracted from the zfb build pipeline to decouple expensive git operations from the site build.

It operates in two modes:

- **Server mode** -- runs an HTTP server during local development, serving history on demand
- **CLI mode** -- batch-generates all history JSON files for production builds in CI

This separation enables a parallel CI strategy where the zfb site build and history generation run as independent jobs, reducing total build time.

## Server Mode (Local Development)

The server runs on a configurable port (default 4322) and serves document history via a REST API. A project scaffolded with `docHistory` enabled gets this wired automatically: `create-zudo-doc` emits a `dev` script of `run-parallel dev:zfb dev:history`, so zfb and the doc-history-server run concurrently. `run-parallel` is a small bin shipped by `@takazudo/zudo-doc`, so this needs no extra dev dependency.

### Starting the Server

Run it through the generated `dev:history` script — `doc-history-server` lives only in the project's `node_modules/.bin`, which package-manager scripts put on `PATH` automatically but a bare shell prompt does not:

```sh
pnpm dev:history
# or: npm run dev:history
```

Under the hood, that script resolves to:

```sh
doc-history-server --port 4322 --content-dir src/content/docs
```

When additional locales are configured, `create-zudo-doc` appends one
`--locale <lang>:src/content/docs-<lang>` flag for every configured locale, in
order. For example, EN + JA + DE emits `--locale ja:src/content/docs-ja
--locale de:src/content/docs-de`. To run the server manually with other flags,
see [Server Options](https://zudo-doc.takazudomodular.com/docs/reference/doc-history-server#server-mode-local-development-server-options) below.

### Endpoints

#### GET /doc-history/\{slug\}.json

Returns the full git history for a document.

```
GET /doc-history/guides/writing-docs.json
```

#### GET /doc-history/\{locale\}/\{slug\}.json

Returns history for a localized document.

```
GET /doc-history/ja/guides/writing-docs.json
```

#### GET /health

Health check endpoint.

```json
{ "status": "ok" }
```

### Server Options

| Flag | Required | Default | Description |
| ---- | -------- | ------- | ----------- |
| `--content-dir` | Yes | -- | Content directory to scan for documents |
| `--locale <key>:<dir>` | No | -- | Additional locale directory (repeatable) |
| `--port` | No | `4322` | HTTP server port |
| `--max-entries` | No | `50` | Maximum git commits per document |

### Server Behavior

- File index refreshes every 10 seconds, automatically picking up new or renamed files
- CORS headers are included for cross-origin dev access
- Requests for unknown slugs return 404 with `{ "error": "No doc found for slug: ..." }`

### zfb Integration

In dev mode, the doc-history zfb plugin's `devMiddleware` hook proxies `/doc-history/*` requests to this server. This means the history panel in the browser transparently fetches from the standalone server through the zfb dev server.

A `docHistory`-enabled project's generated `dev` script (`run-parallel dev:zfb dev:history`) runs both zfb (port 4321) and the doc-history-server (port 4322) concurrently.

## CLI Mode (CI Builds)

The CLI generates static JSON files for all documents, intended for CI pipelines.

### Usage

```sh
pnpm generate -- --content-dir src/content/docs --locale ja:src/content/docs-ja --out-dir dist/doc-history
```

### CLI Options

| Flag | Required | Default | Description |
| ---- | -------- | ------- | ----------- |
| `--content-dir` | Yes | -- | Content directory to scan |
| `--locale <key>:<dir>` | No | -- | Additional locale directory (repeatable) |
| `--out-dir` | Yes | -- | Output directory for generated JSON files |
| `--max-entries` | No | `50` | Maximum git commits per document |

### CI Pipeline Integration

The CI pipeline runs history generation as a parallel job alongside the zfb site build:

- **build-site** job: full clone (`fetch-depth: 0`), builds the zfb site with `pnpm build` (no `SKIP_DOC_HISTORY` — the preBuild step runs `git log --follow` to populate `.zfb/doc-history-meta.json` with real dates, inlined into the SSG HTML as the visible Created/Updated/Author block)
- **build-history** job: full clone (`fetch-depth: 0`), runs `@takazudo/zudo-doc-history-server generate`
- **deploy** job: merges both artifacts and deploys to Cloudflare Workers

This parallelization is possible because the history generation is fully independent of the zfb build.

[Dynamic content unavailable in text export]

Both the `build-history` and `build-site` jobs require a full clone (`fetch-depth: 0`). `build-history` needs the complete commit history to extract revision data for the DocHistory island dropdown. `build-site` needs it so the preBuild step can populate the SSG meta block (Created/Updated/Author) with real git dates.



[Dynamic content unavailable in text export]

`DOC_HISTORY_SKIP_POSTBUILD` (see the [deployment guide](https://zudo-doc.takazudomodular.com/docs/guides/deployment)) only gates the zfb plugin's inline postBuild hook running inside `build-site` — it has no effect on this standalone `generate` CLI, which is what the `build-history` job actually runs and always generates when invoked.



## Data Format

### DocHistoryEntry

A single git revision entry for a document:

```ts
interface DocHistoryEntry {
  /** Full commit hash (use .slice(0, 7) for display) */
  hash: string;
  /** ISO 8601 date string */
  date: string;
  /** Commit author name */
  author: string;
  /** First line of commit message */
  message: string;
  /** Full file content at this revision */
  content: string;
}
```

### DocHistoryData

Complete history data for a single document:

```ts
interface DocHistoryData {
  /** Document slug (route path) */
  slug: string;
  /** Relative file path in the repository */
  filePath: string;
  /** Git revision entries, newest first */
  entries: DocHistoryEntry[];
}
```

### Output Structure

Generated files mirror the content directory structure:

```
dist/doc-history/
  getting-started.json
  guides/writing-docs.json
  guides/color.json
  ja/getting-started.json
  ja/guides/writing-docs.json
```

## Architecture

### Git Operations

History extraction uses non-blocking `execFile` / `spawn` calls for:

- `git log` -- commit listing with `--follow` for rename tracking
- `git show` -- file content at each revision

The `--follow` flag tracks file history across renames with multiple fallback strategies, so documents that have been moved or renamed retain their complete history.

### Key Design Decisions

- **One async extraction path** -- both the dev server and CI CLI await `getDocHistoryAsync`. The CLI uses bounded parallelism with a semaphore limited to `Math.min(8, Math.max(2, cpus().length))` concurrent workers; only the cached one-time repository-root probe is synchronous
- **Repo-relative paths** -- API responses use relative file paths to avoid leaking absolute server paths
- **Standalone package** -- decoupled from zfb to enable parallel CI builds and independent versioning

## Sub-Package Location

```
packages/doc-history-server/
├── src/
│   ├── index.ts        # Server entry — parses args, starts HTTP server
│   ├── cli.ts          # CLI entry — parses args, batch generates JSONs
│   ├── args.ts         # Shared argument parsing with bounds checking
│   ├── server.ts       # HTTP server (REST API endpoints)
│   ├── git-history.ts  # Core git logic (log, show, follow, rename tracking)
│   ├── shared.ts       # Shared helpers (getContentDirEntries)
│   └── types.ts        # DocHistoryEntry, DocHistoryData types
├── package.json
├── tsconfig.json
└── README.md
```