Skip to main content

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.
All endpoints require session-based authentication through NextAuth. File paths must resolve to a location within the project directory.

Read file with hashes

GET /api/hashline?path=/src/index.ts
Returns every line of a file annotated with its content hash.

Query parameters

ParameterTypeRequiredDefaultDescription
pathstringYesFilesystem path to the file
formatstringNojsonResponse format: json or cli

Response (JSON format)

{
  "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| ..."
}
FieldTypeDescription
pathstringThe requested file path
stats.totalLinesnumberTotal number of lines in the file
stats.blankLinesnumberNumber of blank lines
stats.uniqueHashesnumberNumber of distinct hash values
stats.hashCollisionsnumberNumber of lines that share a hash with another line
linesarrayArray of line objects
lines[].lineNumbernumber1-indexed line number
lines[].hashstringShort content hash for this line
lines[].contentstringText content of the line
lines[].isBlankbooleanWhether the line is empty or whitespace-only
formattedstringPre-formatted output with the pattern `lineNumber#hashcontent`

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

CodeDescription
400path parameter required — missing path query parameter
401Unauthorized — no valid session
403Invalid path: must be within project directory — path resolves outside the project root
500File read failure (for example, file does not exist)

Apply an edit

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)

FieldTypeRequiredDefaultDescription
pathstringYesFile path to edit
hashRefstringYesHash reference in the format lineNumber#hash or #hash
newContentstringYesReplacement content for the matched line
backupbooleanNotrueCreate a timestamped backup before editing
{
  "path": "/src/index.ts",
  "hashRef": "12#A3",
  "newContent": "import { z } from 'y'"
}

Request body (batch edit)

FieldTypeRequiredDefaultDescription
pathstringYesFile path to edit
editsarrayYesArray of edit objects
edits[].hashRefstringYesHash reference for the line to edit
edits[].newContentstringYesReplacement content
backupbooleanNotrueCreate a timestamped backup before editing
{
  "path": "/src/index.ts",
  "edits": [
    { "hashRef": "12#A3", "newContent": "import { z } from 'y'" },
    { "hashRef": "15#B7", "newContent": "const x = 10" }
  ]
}

Response (single edit)

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

Response (batch edit)

{
  "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:
{
  "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

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

Delete a backup

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

ParameterTypeRequiredDescription
pathstringYesPath to the backup file. Must contain .backup. in the name.

Response

{
  "success": true,
  "message": "Deleted: /src/index.ts.backup.1712000000"
}

Errors

CodeDescription
400path parameter required — missing path query parameter
401Unauthorized — no valid session
403Can only delete .backup. files — path does not contain .backup.
403Invalid path — path resolves outside the project directory
500Delete 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.