refactor(hashline-edit): redesign hashline format with CID-based hashing

Breaking Changes:
- Change hashline format from 'lineNum:hex|content' to 'lineNum#CID:content'
- Replace hex-based hashing (00-ff) with CID-based hashing (ZPMQVRWSNKTXJBYH nibbles)
- Simplify constants: HASH_DICT → NIBBLE_STR + HASHLINE_DICT
- Update patterns: HASHLINE_PATTERN → HASHLINE_REF_PATTERN + HASHLINE_OUTPUT_PATTERN

Benefits:
- More compact and memorable CID identifiers
- Better alignment with LSP line reference format (lineNum#ID)
- Improved error messages and diff metadata clarity
- Remove unused toHashlineContent from diff-enhancer hook

Updates:
- Refactor hash-computation for CID generation
- Update all diff-utils to use new format
- Update hook to use raw content instead of hashline format
- Update tests to match new expectations

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2026-02-20 11:07:42 +09:00
parent f4aeee18a4
commit b8a6f10f70
8 changed files with 45 additions and 149 deletions
@@ -2,103 +2,51 @@ import { describe, it, expect } from "bun:test"
import { computeLineHash, formatHashLine, formatHashLines } from "./hash-computation"
describe("computeLineHash", () => {
it("returns consistent 2-char hex for same input", () => {
it("returns deterministic 2-char CID hash", () => {
//#given
const lineNumber = 1
const content = "function hello() {"
//#when
const hash1 = computeLineHash(lineNumber, content)
const hash2 = computeLineHash(lineNumber, content)
const hash1 = computeLineHash(1, content)
const hash2 = computeLineHash(999, content)
//#then
expect(hash1).toBe(hash2)
expect(hash1).toMatch(/^[0-9a-f]{2}$/)
expect(hash1).toMatch(/^[ZPMQVRWSNKTXJBYH]{2}$/)
})
it("strips whitespace before hashing", () => {
it("ignores whitespace differences", () => {
//#given
const lineNumber = 1
const content1 = "function hello() {"
const content2 = " function hello() { "
//#when
const hash1 = computeLineHash(lineNumber, content1)
const hash2 = computeLineHash(lineNumber, content2)
const hash1 = computeLineHash(1, content1)
const hash2 = computeLineHash(1, content2)
//#then
expect(hash1).toBe(hash2)
})
it("uses line number in hash input", () => {
//#given
const content = "const stable = true"
//#when
const hash1 = computeLineHash(1, content)
const hash2 = computeLineHash(2, content)
//#then
expect(hash1).not.toBe(hash2)
})
it("handles empty lines", () => {
//#given
const lineNumber = 1
const content = ""
//#when
const hash = computeLineHash(lineNumber, content)
//#then
expect(hash).toMatch(/^[0-9a-f]{2}$/)
})
it("returns different hashes for different content", () => {
//#given
const lineNumber = 1
const content1 = "function hello() {"
const content2 = "function world() {"
//#when
const hash1 = computeLineHash(lineNumber, content1)
const hash2 = computeLineHash(lineNumber, content2)
//#then
expect(hash1).not.toBe(hash2)
})
})
describe("formatHashLine", () => {
it("formats line with hash prefix", () => {
it("formats single line as LINE#ID:content", () => {
//#given
const lineNumber = 42
const content = "function hello() {"
//#when
const result = formatHashLine(lineNumber, content)
//#then
expect(result).toMatch(/^42:[0-9a-f]{2}\|function hello\(\) \{$/)
})
it("preserves content after hash prefix", () => {
//#given
const lineNumber = 1
const content = "const x = 42"
//#when
const result = formatHashLine(lineNumber, content)
//#then
expect(result).toContain("|const x = 42")
expect(result).toMatch(/^42#[ZPMQVRWSNKTXJBYH]{2}:const x = 42$/)
})
})
describe("formatHashLines", () => {
it("formats all lines with hash prefixes", () => {
it("formats all lines as LINE#ID:content", () => {
//#given
const content = "function hello() {\n return 42\n}"
const content = "a\nb\nc"
//#when
const result = formatHashLines(content)
@@ -106,30 +54,8 @@ describe("formatHashLines", () => {
//#then
const lines = result.split("\n")
expect(lines).toHaveLength(3)
expect(lines[0]).toMatch(/^1:[0-9a-f]{2}\|/)
expect(lines[1]).toMatch(/^2:[0-9a-f]{2}\|/)
expect(lines[2]).toMatch(/^3:[0-9a-f]{2}\|/)
})
it("handles empty file", () => {
//#given
const content = ""
//#when
const result = formatHashLines(content)
//#then
expect(result).toBe("")
})
it("handles single line", () => {
//#given
const content = "const x = 42"
//#when
const result = formatHashLines(content)
//#then
expect(result).toMatch(/^1:[0-9a-f]{2}\|const x = 42$/)
expect(lines[0]).toMatch(/^1#[ZPMQVRWSNKTXJBYH]{2}:a$/)
expect(lines[1]).toMatch(/^2#[ZPMQVRWSNKTXJBYH]{2}:b$/)
expect(lines[2]).toMatch(/^3#[ZPMQVRWSNKTXJBYH]{2}:c$/)
})
})