> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentbot.raveculture.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Hashline

> Content-addressed file editing using line-level hashes to prevent stale-line errors

# Hashline

Read and edit files using content-addressed hashes instead of plain line numbers. Each line is identified by a combined `lineNumber#hash` reference, so edits fail predictably when the file has changed since you last read it.

<Note>All endpoints require an authenticated **admin** session. The caller's email must appear in the server's `ADMIN_EMAILS` allowlist; ordinary authenticated users receive `403 Forbidden`. File paths must resolve to a location within the project directory.</Note>

<Warning>Hashline is a read-any/write-any-file primitive scoped to the project directory. Admin-only access is enforced because GET, POST, and DELETE all touch arbitrary files under the project root.</Warning>

## Read file with hashes

```http theme={"dark"}
GET /api/hashline?path=/src/index.ts
```

Returns every line of a file annotated with its content hash.

### Query parameters

| Parameter | Type   | Required | Default | Description                      |
| --------- | ------ | -------- | ------- | -------------------------------- |
| `path`    | string | Yes      | --      | Filesystem path to the file      |
| `format`  | string | No       | `json`  | Response format: `json` or `cli` |

### Response (JSON format)

```json theme={"dark"}
{
  "path": "/src/index.ts",
  "stats": {
    "totalLines": 42,
    "blankLines": 5,
    "uniqueHashes": 38,
    "hashCollisions": 2
  },
  "lines": [
    {
      "lineNumber": 1,
      "hash": "A3",
      "content": "import { x } from 'y'",
      "isBlank": false
    }
  ],
  "formatted": "   1#A3| import { x } from 'y'\n   2#B7| ..."
}
```

| Field                  | Type    | Description                                             |           |
| ---------------------- | ------- | ------------------------------------------------------- | --------- |
| `path`                 | string  | The requested file path                                 |           |
| `stats.totalLines`     | number  | Total number of lines in the file                       |           |
| `stats.blankLines`     | number  | Number of blank lines                                   |           |
| `stats.uniqueHashes`   | number  | Number of distinct hash values                          |           |
| `stats.hashCollisions` | number  | Number of lines that share a hash with another line     |           |
| `lines`                | array   | Array of line objects                                   |           |
| `lines[].lineNumber`   | number  | 1-indexed line number                                   |           |
| `lines[].hash`         | string  | Short content hash for this line                        |           |
| `lines[].content`      | string  | Text content of the line                                |           |
| `lines[].isBlank`      | boolean | Whether the line is empty or whitespace-only            |           |
| `formatted`            | string  | Pre-formatted output with the pattern \`lineNumber#hash | content\` |

### Response (CLI format)

When `format=cli`, the response is plain text with `Content-Type: text/plain`. Each line is formatted as:

```
   1#A3| import { x } from 'y'
   2#B7| const config = {}
```

### Errors

| Code | Description                                                                                |
| ---- | ------------------------------------------------------------------------------------------ |
| 400  | `path parameter required` -- missing `path` query parameter                                |
| 401  | `Unauthorized` -- no valid session                                                         |
| 403  | `Forbidden` -- session is valid but the caller is not in the admin allowlist               |
| 403  | `Invalid path: must be within project directory` -- path resolves outside the project root |
| 500  | File read failure (for example, file does not exist)                                       |

## Apply an edit

```http theme={"dark"}
POST /api/hashline
```

Edit one or more lines by hash reference. If the hash no longer matches the current file content, the request fails with a `409` and suggests similar lines.

### Request body (single edit)

| Field        | Type    | Required | Default | Description                                               |
| ------------ | ------- | -------- | ------- | --------------------------------------------------------- |
| `path`       | string  | Yes      | --      | File path to edit                                         |
| `hashRef`    | string  | Yes      | --      | Hash reference in the format `lineNumber#hash` or `#hash` |
| `newContent` | string  | Yes      | --      | Replacement content for the matched line                  |
| `backup`     | boolean | No       | `true`  | Create a timestamped backup before editing                |

```json theme={"dark"}
{
  "path": "/src/index.ts",
  "hashRef": "12#A3",
  "newContent": "import { z } from 'y'"
}
```

### Request body (batch edit)

| Field                | Type    | Required | Default | Description                                |
| -------------------- | ------- | -------- | ------- | ------------------------------------------ |
| `path`               | string  | Yes      | --      | File path to edit                          |
| `edits`              | array   | Yes      | --      | Array of edit objects                      |
| `edits[].hashRef`    | string  | Yes      | --      | Hash reference for the line to edit        |
| `edits[].newContent` | string  | Yes      | --      | Replacement content                        |
| `backup`             | boolean | No       | `true`  | Create a timestamped backup before editing |

```json theme={"dark"}
{
  "path": "/src/index.ts",
  "edits": [
    { "hashRef": "12#A3", "newContent": "import { z } from 'y'" },
    { "hashRef": "15#B7", "newContent": "const x = 10" }
  ]
}
```

### Response (single edit)

```json theme={"dark"}
{
  "success": true,
  "path": "/src/index.ts",
  "edit": {
    "success": true,
    "lineNumber": 12,
    "oldContent": "import { x } from 'y'",
    "newContent": "import { z } from 'y'"
  }
}
```

### Response (batch edit)

```json theme={"dark"}
{
  "success": true,
  "path": "/src/index.ts",
  "results": [
    { "success": true, "lineNumber": 12, "newContent": "import { z } from 'y'" },
    { "success": true, "lineNumber": 15, "newContent": "const x = 10" }
  ]
}
```

The top-level `success` is `true` only when every edit in the batch succeeds.

### Stale line recovery

When a hash reference does not match any line in the current file, the API returns a `409` with suggestions:

```json theme={"dark"}
{
  "error": "Line 12 hash A3 does not match current content",
  "suggestion": "Similar lines found:",
  "similarLines": [
    { "lineNumber": 5, "hash": "B7", "content": "import { x } from 'z'" }
  ]
}
```

Up to 5 similar lines are returned. Re-read the file with `GET /api/hashline` and retry with an updated hash reference.

### Errors

| Code | Description                                                                                                        |
| ---- | ------------------------------------------------------------------------------------------------------------------ |
| 400  | `path required` -- missing `path` in request body                                                                  |
| 400  | `hashRef and newContent required` -- single edit mode with missing fields                                          |
| 401  | `Unauthorized` -- no valid session                                                                                 |
| 403  | `Forbidden` -- session is valid but the caller is not in the admin allowlist                                       |
| 403  | `Invalid path: must be within project directory` -- path traversal attempt                                         |
| 409  | Stale line -- the hash reference does not match the current file. Response includes `similarLines` when available. |
| 500  | Edit failed for a reason other than a stale reference                                                              |

## Delete a backup

```http theme={"dark"}
DELETE /api/hashline?path=/src/index.ts.backup.1712000000
```

Remove a backup file created by a previous edit. Only files containing `.backup.` in their name can be deleted through this endpoint.

### Query parameters

| Parameter | Type   | Required | Description                                                   |
| --------- | ------ | -------- | ------------------------------------------------------------- |
| `path`    | string | Yes      | Path to the backup file. Must contain `.backup.` in the name. |

### Response

```json theme={"dark"}
{
  "success": true,
  "message": "Deleted: /src/index.ts.backup.1712000000"
}
```

### Errors

| Code | Description                                                                  |
| ---- | ---------------------------------------------------------------------------- |
| 400  | `path parameter required` -- missing `path` query parameter                  |
| 401  | `Unauthorized` -- no valid session                                           |
| 403  | `Forbidden` -- session is valid but the caller is not in the admin allowlist |
| 403  | `Can only delete .backup. files` -- path does not contain `.backup.`         |
| 403  | `Invalid path` -- path resolves outside the project directory                |
| 500  | Delete failed (for example, file does not exist)                             |

## Hash reference format

A hash reference combines a line number and a short content hash:

```
lineNumber#hash
```

For example, `12#A3` refers to line 12 with hash `A3`. You can also use `#A3` without the line number, though including the line number improves match accuracy when hashes collide.

The hash is derived from the trimmed content of the line. Two lines with identical content after trimming share the same hash. The `stats.hashCollisions` field in the read response tells you how many lines share a hash.
