@agimon-ai/doompi-hashline 0.0.1-alpha.6 → 0.0.1-alpha.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/files.cjs +1 -1
  2. package/dist/files.d.mts +1 -1
  3. package/dist/files.mjs +1 -1
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.d.mts +1 -1
  6. package/dist/index.mjs +1 -1
  7. package/dist/{adapters/node/files.cjs → services/files/index.cjs} +1 -1
  8. package/dist/services/files/index.cjs.map +1 -0
  9. package/dist/services/files/index.d.mts +11 -0
  10. package/dist/services/files/index.d.mts.map +1 -0
  11. package/dist/{adapters/node/files.mjs → services/files/index.mjs} +1 -1
  12. package/dist/services/files/index.mjs.map +1 -0
  13. package/dist/services/{hashline.cjs → hashline/index.cjs} +2 -2
  14. package/dist/services/hashline/index.cjs.map +1 -0
  15. package/dist/services/hashline/index.d.mts +20 -0
  16. package/dist/services/hashline/index.d.mts.map +1 -0
  17. package/dist/services/{hashline.mjs → hashline/index.mjs} +2 -2
  18. package/dist/services/hashline/index.mjs.map +1 -0
  19. package/dist/types/hashline.d.mts +8 -9
  20. package/dist/types/hashline.d.mts.map +1 -1
  21. package/package.json +13 -5
  22. package/dist/adapters/node/files.cjs.map +0 -1
  23. package/dist/adapters/node/files.d.cts +0 -12
  24. package/dist/adapters/node/files.d.cts.map +0 -1
  25. package/dist/adapters/node/files.d.mts +0 -12
  26. package/dist/adapters/node/files.d.mts.map +0 -1
  27. package/dist/adapters/node/files.mjs.map +0 -1
  28. package/dist/files.d.cts +0 -2
  29. package/dist/index.d.cts +0 -3
  30. package/dist/services/hashline.cjs.map +0 -1
  31. package/dist/services/hashline.d.cts +0 -21
  32. package/dist/services/hashline.d.cts.map +0 -1
  33. package/dist/services/hashline.d.mts +0 -21
  34. package/dist/services/hashline.d.mts.map +0 -1
  35. package/dist/services/hashline.mjs.map +0 -1
  36. package/dist/types/hashline.d.cts +0 -34
  37. package/dist/types/hashline.d.cts.map +0 -1
package/dist/files.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/node/files.cjs");exports.computeFileTag=e.computeFileTag,exports.decodeUtf8=e.decodeUtf8,exports.displayPath=e.displayPath,exports.isWritableFile=e.isWritableFile,exports.resolveInputPath=e.resolveInputPath,exports.resolveReadInputPath=e.resolveReadInputPath;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/files/index.cjs");exports.computeFileTag=e.computeFileTag,exports.decodeUtf8=e.decodeUtf8,exports.displayPath=e.displayPath,exports.isWritableFile=e.isWritableFile,exports.resolveInputPath=e.resolveInputPath,exports.resolveReadInputPath=e.resolveReadInputPath;
package/dist/files.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath } from "./adapters/node/files.mjs";
1
+ import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath } from "./services/files/index.mjs";
2
2
  export { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath };
package/dist/files.mjs CHANGED
@@ -1 +1 @@
1
- import{computeFileTag as e,decodeUtf8 as t,displayPath as n,isWritableFile as r,resolveInputPath as i,resolveReadInputPath as a}from"./adapters/node/files.mjs";export{e as computeFileTag,t as decodeUtf8,n as displayPath,r as isWritableFile,i as resolveInputPath,a as resolveReadInputPath};
1
+ import{computeFileTag as e,decodeUtf8 as t,displayPath as n,isWritableFile as r,resolveInputPath as i,resolveReadInputPath as a}from"./services/files/index.mjs";export{e as computeFileTag,t as decodeUtf8,n as displayPath,r as isWritableFile,i as resolveInputPath,a as resolveReadInputPath};
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/hashline.cjs");exports.applyHashlineEdits=e.applyHashlineEdits,exports.formatFileHeader=e.formatFileHeader,exports.formatTaggedLine=e.formatTaggedLine,exports.formatTaggedLines=e.formatTaggedLines,exports.hashLine=e.hashLine,exports.normalizeFileTag=e.normalizeFileTag,exports.normalizeToLf=e.normalizeToLf,exports.parseFileHeader=e.parseFileHeader,exports.parseLineAnchor=e.parseLineAnchor,exports.parseTaggedLine=e.parseTaggedLine,exports.splitLines=e.splitLines,exports.stripBom=e.stripBom;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/hashline/index.cjs");exports.applyHashlineEdits=e.applyHashlineEdits,exports.formatFileHeader=e.formatFileHeader,exports.formatTaggedLine=e.formatTaggedLine,exports.formatTaggedLines=e.formatTaggedLines,exports.hashLine=e.hashLine,exports.normalizeFileTag=e.normalizeFileTag,exports.normalizeToLf=e.normalizeToLf,exports.parseFileHeader=e.parseFileHeader,exports.parseLineAnchor=e.parseLineAnchor,exports.parseTaggedLine=e.parseTaggedLine,exports.splitLines=e.splitLines,exports.stripBom=e.stripBom;
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, PreparedHashlineEdit, TaggedLineMarker, TaggedLinePrefix } from "./types/hashline.mjs";
2
- import { applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom } from "./services/hashline.mjs";
2
+ import { applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom } from "./services/hashline/index.mjs";
3
3
  export { type AppliedHashlineEdits, type FileHeader, type HashlineRangeInput, type LineAnchor, type ParsedTaggedLine, type PreparedHashlineEdit, type TaggedLineMarker, type TaggedLinePrefix, applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{applyHashlineEdits as e,formatFileHeader as t,formatTaggedLine as n,formatTaggedLines as r,hashLine as i,normalizeFileTag as a,normalizeToLf as o,parseFileHeader as s,parseLineAnchor as c,parseTaggedLine as l,splitLines as u,stripBom as d}from"./services/hashline.mjs";export{e as applyHashlineEdits,t as formatFileHeader,n as formatTaggedLine,r as formatTaggedLines,i as hashLine,a as normalizeFileTag,o as normalizeToLf,s as parseFileHeader,c as parseLineAnchor,l as parseTaggedLine,u as splitLines,d as stripBom};
1
+ import{applyHashlineEdits as e,formatFileHeader as t,formatTaggedLine as n,formatTaggedLines as r,hashLine as i,normalizeFileTag as a,normalizeToLf as o,parseFileHeader as s,parseLineAnchor as c,parseTaggedLine as l,splitLines as u,stripBom as d}from"./services/hashline/index.mjs";export{e as applyHashlineEdits,t as formatFileHeader,n as formatTaggedLine,r as formatTaggedLines,i as hashLine,a as normalizeFileTag,o as normalizeToLf,s as parseFileHeader,c as parseLineAnchor,l as parseTaggedLine,u as splitLines,d as stripBom};
@@ -1,2 +1,2 @@
1
1
  let e=require("node:crypto"),t=require("node:fs"),n=require("node:fs/promises"),r=require("node:os"),i=require("node:path"),a=require("node:url");const o=/[\u00A0\u2000-\u200A\u202F\u205F\u3000]/gu;function s(t){return(0,e.createHash)(`sha256`).update(t).digest(`base64url`).slice(0,8)}function c(e,t){try{return new TextDecoder(`utf-8`,{fatal:!0}).decode(e)}catch(e){throw Error(`Cannot use hashline tools on ${t}: the file is not valid UTF-8.`,{cause:e})}}async function l(e){return(0,n.access)(e,t.constants.W_OK).then(()=>!0,()=>!1)}function u(e,t){let n=e.replace(o,` `);return n.startsWith(`@`)&&(n=n.slice(1)),process.platform===`win32`&&(n=m(n)),n===`~`?n=(0,r.homedir)():n.startsWith(`~${i.sep}`)?n=(0,i.resolve)((0,r.homedir)(),n.slice(2)):n.startsWith(`file://`)&&(n=(0,a.fileURLToPath)(n)),(0,i.isAbsolute)(n)?(0,i.resolve)(n):(0,i.resolve)(t,n)}async function d(e,t){let n=u(e,t);if(await p(n))return n;let r=[n.replace(/ (AM|PM)\./giu,` $1.`),n.normalize(`NFD`),n.replaceAll(`'`,`’`),n.normalize(`NFD`).replaceAll(`'`,`’`)];for(let e of r)if(e!==n&&await p(e))return e;return n}function f(e,t){let n=(0,i.relative)(t,e),r=(n!==``&&n!==`..`&&!n.startsWith(`..${i.sep}`)&&!(0,i.isAbsolute)(n)?n:e).split(i.sep).join(`/`);return r===`~`||r.startsWith(`~/`)?`./${r}`:r}async function p(e){return(0,n.access)(e).then(()=>!0,()=>!1)}function m(e){if(!e.startsWith(`/`)||e.startsWith(`//`)||e.includes(`\\`))return e;let t=/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/iu.exec(e);if(!t?.[1])return e;let n=t[2]?.replaceAll(`/`,`\\`);return`${t[1].toUpperCase()}:\\${n??``}`}exports.computeFileTag=s,exports.decodeUtf8=c,exports.displayPath=f,exports.isWritableFile=l,exports.resolveInputPath=u,exports.resolveReadInputPath=d;
2
- //# sourceMappingURL=files.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["createHash","access","constants","homedir","sep","resolve","fileURLToPath","isAbsolute","relative"],"sources":["../../../src/services/files/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { access } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/gu;\nconst NARROW_NO_BREAK_SPACE = '\\u202f';\n\nexport function computeFileTag(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('base64url').slice(0, 8);\n}\n\nexport function decodeUtf8(bytes: Uint8Array, path: string): string {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch (error) {\n throw new Error(`Cannot use hashline tools on ${path}: the file is not valid UTF-8.`, { cause: error });\n }\n}\n\n/** Return whether the current process can edit an existing file. */\nexport async function isWritableFile(path: string): Promise<boolean> {\n return access(path, constants.W_OK).then(\n () => true,\n () => false,\n );\n}\n\nexport function resolveInputPath(input: string, cwd: string): string {\n let path = input.replace(UNICODE_SPACES, ' ');\n if (path.startsWith('@')) path = path.slice(1);\n if (process.platform === 'win32') path = normalizeWindowsShellPath(path);\n if (path === '~') path = homedir();\n else if (path.startsWith(`~${sep}`)) path = resolve(homedir(), path.slice(2));\n else if (path.startsWith('file://')) path = fileURLToPath(path);\n return isAbsolute(path) ? resolve(path) : resolve(cwd, path);\n}\n\n/** Resolve the same existing-file variants as Pi's native read tool. */\nexport async function resolveReadInputPath(input: string, cwd: string): Promise<string> {\n const resolved = resolveInputPath(input, cwd);\n if (await pathExists(resolved)) return resolved;\n\n const variants = [\n resolved.replace(/ (AM|PM)\\./giu, `${NARROW_NO_BREAK_SPACE}$1.`),\n resolved.normalize('NFD'),\n resolved.replaceAll(\"'\", '\\u2019'),\n resolved.normalize('NFD').replaceAll(\"'\", '\\u2019'),\n ];\n for (const candidate of variants) {\n if (candidate !== resolved && (await pathExists(candidate))) return candidate;\n }\n return resolved;\n}\n\nexport function displayPath(absolutePath: string, cwd: string): string {\n const path = relative(cwd, absolutePath);\n const display =\n path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path) ? path : absolutePath;\n const portable = display.split(sep).join('/');\n return portable === '~' || portable.startsWith('~/') ? `./${portable}` : portable;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n return access(path).then(\n () => true,\n () => false,\n );\n}\n\nfunction normalizeWindowsShellPath(path: string): string {\n if (!path.startsWith('/') || path.startsWith('//') || path.includes('\\\\')) return path;\n const match = /^\\/(?:mnt\\/|cygdrive\\/)?([a-z])(?:\\/(.*))?$/iu.exec(path);\n if (!match?.[1]) return path;\n const suffix = match[2]?.replaceAll('/', '\\\\');\n return `${match[1].toUpperCase()}:\\\\${suffix ?? ''}`;\n}\n"],"mappings":"kJAOA,MAAM,EAAiB,4CAGvB,SAAgB,EAAe,EAA2B,CACxD,OAAA,EAAOA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,EAAG,CAAC,CAC1E,CAEA,SAAgB,EAAW,EAAmB,EAAsB,CAClE,GAAI,CACF,OAAO,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,CAAC,CAAC,OAAO,CAAK,CAC/D,OAAS,EAAO,CACd,MAAU,MAAM,gCAAgC,EAAK,gCAAiC,CAAE,MAAO,CAAM,CAAC,CACxG,CACF,CAGA,eAAsB,EAAe,EAAgC,CACnE,OAAA,EAAOC,EAAAA,OAAAA,CAAO,EAAMC,EAAAA,UAAU,IAAI,CAAC,CAAC,SAC5B,OACA,EACR,CACF,CAEA,SAAgB,EAAiB,EAAe,EAAqB,CACnE,IAAI,EAAO,EAAM,QAAQ,EAAgB,GAAG,EAM5C,OALI,EAAK,WAAW,GAAG,IAAG,EAAO,EAAK,MAAM,CAAC,GACzC,QAAQ,WAAa,UAAS,EAAO,EAA0B,CAAI,GACnE,IAAS,IAAK,GAAA,EAAOC,EAAAA,QAAAA,CAAQ,EACxB,EAAK,WAAW,IAAIC,EAAAA,KAAK,EAAG,GAAA,EAAOC,EAAAA,QAAAA,EAAAA,EAAQF,EAAAA,QAAAA,CAAQ,EAAG,EAAK,MAAM,CAAC,CAAC,EACnE,EAAK,WAAW,SAAS,IAAG,GAAA,EAAOG,EAAAA,cAAAA,CAAc,CAAI,IAC9D,EAAOC,EAAAA,WAAAA,CAAW,CAAI,GAAA,EAAIF,EAAAA,QAAAA,CAAQ,CAAI,GAAA,EAAIA,EAAAA,QAAAA,CAAQ,EAAK,CAAI,CAC7D,CAGA,eAAsB,EAAqB,EAAe,EAA8B,CACtF,IAAM,EAAW,EAAiB,EAAO,CAAG,EAC5C,GAAI,MAAM,EAAW,CAAQ,EAAG,OAAO,EAEvC,IAAM,EAAW,CACf,EAAS,QAAQ,gBAAiB,MAA6B,EAC/D,EAAS,UAAU,KAAK,EACxB,EAAS,WAAW,IAAK,GAAQ,EACjC,EAAS,UAAU,KAAK,CAAC,CAAC,WAAW,IAAK,GAAQ,CACpD,EACA,IAAK,IAAM,KAAa,EACtB,GAAI,IAAc,GAAa,MAAM,EAAW,CAAS,EAAI,OAAO,EAEtE,OAAO,CACT,CAEA,SAAgB,EAAY,EAAsB,EAAqB,CACrE,IAAM,GAAA,EAAOG,EAAAA,SAAAA,CAAS,EAAK,CAAY,EAGjC,GADJ,IAAS,IAAM,IAAS,MAAQ,CAAC,EAAK,WAAW,KAAKJ,EAAAA,KAAK,GAAK,EAAA,EAACG,EAAAA,WAAAA,CAAW,CAAI,EAAI,EAAO,EAAA,CACpE,MAAMH,EAAAA,GAAG,CAAC,CAAC,KAAK,GAAG,EAC5C,OAAO,IAAa,KAAO,EAAS,WAAW,IAAI,EAAI,KAAK,IAAa,CAC3E,CAEA,eAAe,EAAW,EAAgC,CACxD,OAAA,EAAOH,EAAAA,OAAAA,CAAO,CAAI,CAAC,CAAC,SACZ,OACA,EACR,CACF,CAEA,SAAS,EAA0B,EAAsB,CACvD,GAAI,CAAC,EAAK,WAAW,GAAG,GAAK,EAAK,WAAW,IAAI,GAAK,EAAK,SAAS,IAAI,EAAG,OAAO,EAClF,IAAM,EAAQ,gDAAgD,KAAK,CAAI,EACvE,GAAI,CAAC,IAAQ,GAAI,OAAO,EACxB,IAAM,EAAS,EAAM,EAAE,EAAE,WAAW,IAAK,IAAI,EAC7C,MAAO,GAAG,EAAM,EAAE,CAAC,YAAY,EAAE,KAAK,GAAU,IAClD"}
@@ -0,0 +1,11 @@
1
+ //#region src/services/files/index.d.ts
2
+ export declare function computeFileTag(bytes: Uint8Array): string;
3
+ export declare function decodeUtf8(bytes: Uint8Array, path: string): string;
4
+ /** Return whether the current process can edit an existing file. */
5
+ export declare function isWritableFile(path: string): Promise<boolean>;
6
+ export declare function resolveInputPath(input: string, cwd: string): string;
7
+ /** Resolve the same existing-file variants as Pi's native read tool. */
8
+ export declare function resolveReadInputPath(input: string, cwd: string): Promise<string>;
9
+ export declare function displayPath(absolutePath: string, cwd: string): string;
10
+ //#endregion
11
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/files/index.ts"],"mappings":";wBAUgB,eAAe,OAAO;wBAItB,WAAW,OAAO,YAAY;;wBASxB,eAAe,eAAe;wBAOpC,iBAAiB,eAAe;;wBAW1B,qBAAqB,eAAe,cAAc;wBAgBxD,YAAY,sBAAsB"}
@@ -1,2 +1,2 @@
1
1
  import{createHash as e}from"node:crypto";import{constants as t}from"node:fs";import{access as n}from"node:fs/promises";import{homedir as r}from"node:os";import{isAbsolute as i,relative as a,resolve as o,sep as s}from"node:path";import{fileURLToPath as c}from"node:url";const l=/[\u00A0\u2000-\u200A\u202F\u205F\u3000]/gu;function u(t){return e(`sha256`).update(t).digest(`base64url`).slice(0,8)}function d(e,t){try{return new TextDecoder(`utf-8`,{fatal:!0}).decode(e)}catch(e){throw Error(`Cannot use hashline tools on ${t}: the file is not valid UTF-8.`,{cause:e})}}async function f(e){return n(e,t.W_OK).then(()=>!0,()=>!1)}function p(e,t){let n=e.replace(l,` `);return n.startsWith(`@`)&&(n=n.slice(1)),process.platform===`win32`&&(n=_(n)),n===`~`?n=r():n.startsWith(`~${s}`)?n=o(r(),n.slice(2)):n.startsWith(`file://`)&&(n=c(n)),i(n)?o(n):o(t,n)}async function m(e,t){let n=p(e,t);if(await g(n))return n;let r=[n.replace(/ (AM|PM)\./giu,` $1.`),n.normalize(`NFD`),n.replaceAll(`'`,`’`),n.normalize(`NFD`).replaceAll(`'`,`’`)];for(let e of r)if(e!==n&&await g(e))return e;return n}function h(e,t){let n=a(t,e),r=(n!==``&&n!==`..`&&!n.startsWith(`..${s}`)&&!i(n)?n:e).split(s).join(`/`);return r===`~`||r.startsWith(`~/`)?`./${r}`:r}async function g(e){return n(e).then(()=>!0,()=>!1)}function _(e){if(!e.startsWith(`/`)||e.startsWith(`//`)||e.includes(`\\`))return e;let t=/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/iu.exec(e);if(!t?.[1])return e;let n=t[2]?.replaceAll(`/`,`\\`);return`${t[1].toUpperCase()}:\\${n??``}`}export{u as computeFileTag,d as decodeUtf8,h as displayPath,f as isWritableFile,p as resolveInputPath,m as resolveReadInputPath};
2
- //# sourceMappingURL=files.mjs.map
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/services/files/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { access } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/gu;\nconst NARROW_NO_BREAK_SPACE = '\\u202f';\n\nexport function computeFileTag(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('base64url').slice(0, 8);\n}\n\nexport function decodeUtf8(bytes: Uint8Array, path: string): string {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch (error) {\n throw new Error(`Cannot use hashline tools on ${path}: the file is not valid UTF-8.`, { cause: error });\n }\n}\n\n/** Return whether the current process can edit an existing file. */\nexport async function isWritableFile(path: string): Promise<boolean> {\n return access(path, constants.W_OK).then(\n () => true,\n () => false,\n );\n}\n\nexport function resolveInputPath(input: string, cwd: string): string {\n let path = input.replace(UNICODE_SPACES, ' ');\n if (path.startsWith('@')) path = path.slice(1);\n if (process.platform === 'win32') path = normalizeWindowsShellPath(path);\n if (path === '~') path = homedir();\n else if (path.startsWith(`~${sep}`)) path = resolve(homedir(), path.slice(2));\n else if (path.startsWith('file://')) path = fileURLToPath(path);\n return isAbsolute(path) ? resolve(path) : resolve(cwd, path);\n}\n\n/** Resolve the same existing-file variants as Pi's native read tool. */\nexport async function resolveReadInputPath(input: string, cwd: string): Promise<string> {\n const resolved = resolveInputPath(input, cwd);\n if (await pathExists(resolved)) return resolved;\n\n const variants = [\n resolved.replace(/ (AM|PM)\\./giu, `${NARROW_NO_BREAK_SPACE}$1.`),\n resolved.normalize('NFD'),\n resolved.replaceAll(\"'\", '\\u2019'),\n resolved.normalize('NFD').replaceAll(\"'\", '\\u2019'),\n ];\n for (const candidate of variants) {\n if (candidate !== resolved && (await pathExists(candidate))) return candidate;\n }\n return resolved;\n}\n\nexport function displayPath(absolutePath: string, cwd: string): string {\n const path = relative(cwd, absolutePath);\n const display =\n path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path) ? path : absolutePath;\n const portable = display.split(sep).join('/');\n return portable === '~' || portable.startsWith('~/') ? `./${portable}` : portable;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n return access(path).then(\n () => true,\n () => false,\n );\n}\n\nfunction normalizeWindowsShellPath(path: string): string {\n if (!path.startsWith('/') || path.startsWith('//') || path.includes('\\\\')) return path;\n const match = /^\\/(?:mnt\\/|cygdrive\\/)?([a-z])(?:\\/(.*))?$/iu.exec(path);\n if (!match?.[1]) return path;\n const suffix = match[2]?.replaceAll('/', '\\\\');\n return `${match[1].toUpperCase()}:\\\\${suffix ?? ''}`;\n}\n"],"mappings":"6QAOA,MAAM,EAAiB,4CAGvB,SAAgB,EAAe,EAA2B,CACxD,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,EAAG,CAAC,CAC1E,CAEA,SAAgB,EAAW,EAAmB,EAAsB,CAClE,GAAI,CACF,OAAO,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,CAAC,CAAC,OAAO,CAAK,CAC/D,OAAS,EAAO,CACd,MAAU,MAAM,gCAAgC,EAAK,gCAAiC,CAAE,MAAO,CAAM,CAAC,CACxG,CACF,CAGA,eAAsB,EAAe,EAAgC,CACnE,OAAO,EAAO,EAAM,EAAU,IAAI,CAAC,CAAC,SAC5B,OACA,EACR,CACF,CAEA,SAAgB,EAAiB,EAAe,EAAqB,CACnE,IAAI,EAAO,EAAM,QAAQ,EAAgB,GAAG,EAM5C,OALI,EAAK,WAAW,GAAG,IAAG,EAAO,EAAK,MAAM,CAAC,GACzC,QAAQ,WAAa,UAAS,EAAO,EAA0B,CAAI,GACnE,IAAS,IAAK,EAAO,EAAQ,EACxB,EAAK,WAAW,IAAI,GAAK,EAAG,EAAO,EAAQ,EAAQ,EAAG,EAAK,MAAM,CAAC,CAAC,EACnE,EAAK,WAAW,SAAS,IAAG,EAAO,EAAc,CAAI,GACvD,EAAW,CAAI,EAAI,EAAQ,CAAI,EAAI,EAAQ,EAAK,CAAI,CAC7D,CAGA,eAAsB,EAAqB,EAAe,EAA8B,CACtF,IAAM,EAAW,EAAiB,EAAO,CAAG,EAC5C,GAAI,MAAM,EAAW,CAAQ,EAAG,OAAO,EAEvC,IAAM,EAAW,CACf,EAAS,QAAQ,gBAAiB,MAA6B,EAC/D,EAAS,UAAU,KAAK,EACxB,EAAS,WAAW,IAAK,GAAQ,EACjC,EAAS,UAAU,KAAK,CAAC,CAAC,WAAW,IAAK,GAAQ,CACpD,EACA,IAAK,IAAM,KAAa,EACtB,GAAI,IAAc,GAAa,MAAM,EAAW,CAAS,EAAI,OAAO,EAEtE,OAAO,CACT,CAEA,SAAgB,EAAY,EAAsB,EAAqB,CACrE,IAAM,EAAO,EAAS,EAAK,CAAY,EAGjC,GADJ,IAAS,IAAM,IAAS,MAAQ,CAAC,EAAK,WAAW,KAAK,GAAK,GAAK,CAAC,EAAW,CAAI,EAAI,EAAO,EAAA,CACpE,MAAM,CAAG,CAAC,CAAC,KAAK,GAAG,EAC5C,OAAO,IAAa,KAAO,EAAS,WAAW,IAAI,EAAI,KAAK,IAAa,CAC3E,CAEA,eAAe,EAAW,EAAgC,CACxD,OAAO,EAAO,CAAI,CAAC,CAAC,SACZ,OACA,EACR,CACF,CAEA,SAAS,EAA0B,EAAsB,CACvD,GAAI,CAAC,EAAK,WAAW,GAAG,GAAK,EAAK,WAAW,IAAI,GAAK,EAAK,SAAS,IAAI,EAAG,OAAO,EAClF,IAAM,EAAQ,gDAAgD,KAAK,CAAI,EACvE,GAAI,CAAC,IAAQ,GAAI,OAAO,EACxB,IAAM,EAAS,EAAM,EAAE,EAAE,WAAW,IAAK,IAAI,EAC7C,MAAO,GAAG,EAAM,EAAE,CAAC,YAAY,EAAE,KAAK,GAAU,IAClD"}
@@ -5,7 +5,7 @@ const e=26n*26n*26n,t=/^[A-Za-z0-9_-]{8}$/u,n=/^@file (.+)#([A-Za-z0-9_-]{8})$/u
5
5
  `)||e.includes(`\r`))throw Error(`Invalid file header path. Expected one non-empty path without line breaks.`);return`@file ${e}#${u(t)}`}function f(e){let t=n.exec(e),r=t?.[1],i=t?.[2];return r===void 0||i===void 0?void 0:{path:r,tag:i}}function p(e,t,n=``,r=e){return y(t),`${n}${t}#${o(r)}|${e}`}function m(e,t=1){return e.map((e,n)=>p(e,t+n)).join(`
6
6
  `)}function h(e){let t=r.exec(e),n=t?.[2],i=t?.[3];if(n===void 0||i===void 0)return;let a=Number.parseInt(n,10);if(!Number.isSafeInteger(a)||a<1)return;let o=v(t?.[1]);return o===void 0?{content:i,line:a}:{content:i,line:a,marker:o}}function g(e){if(e.includes(`
7
7
  `)||e.includes(`\r`))throw Error(`Invalid line anchor: ${e}. Expected one anchor such as 5#abc, not a pasted multiline block.`);let t=i.exec(e);if(!t)throw Error(`Invalid line anchor: ${e}. Expected one anchor such as 5#abc from the latest read or grep result.`);let n=Number.parseInt(t[1]??``,10),r=t[2]?.toLowerCase();if(!Number.isSafeInteger(n)||n<1||r===void 0)throw Error(`Invalid line anchor: ${e}. Expected a positive line number and three letters, such as 5#abc.`);return{line:n,hash:r}}function _(e,t){let n=l(e),r=S(t.map(e=>b(e,n))).sort(C);w(r);let i=[...n];for(let e of[...r].reverse()){let t=e.to.line-e.from.line+1,n=e.content===null||e.content===``?[]:T(e.content);i.splice(e.from.line-1,t,...n)}return{content:i.join(`
8
- `),edits:r}}function v(e){if(e===`>> `)return`match`;if(e===` `)return`context`}function y(e){if(!Number.isSafeInteger(e)||e<1)throw Error(`Invalid tagged line number: ${String(e)}. Expected a positive safe integer.`)}function b(e,t){let n=g(e.from),r=g(e.to);if(n.line>r.line)throw Error(`Invalid edit range ${e.from} to ${e.to}: the starting line follows the ending line.`);if(r.line>t.length)throw Error(`Invalid edit range ${e.from} to ${e.to}: the file has ${t.length} lines.`);return x(n,t),x(r,t),{from:n,to:r,content:e.content??null,source:e}}function x(e,t){let n=t[e.line-1];if(n===void 0)throw Error(`Line ${e.line} is outside the current file.`);let r=o(n);if(r!==e.hash)throw Error(`Stale line anchor ${e.line}#${e.hash}. Current anchor is ${e.line}#${r}. Re-read the file and retry.`)}function S(e){let t=new Map;for(let n of e){let e=JSON.stringify([n.from.line,n.from.hash,n.to.line,n.to.hash,n.content]);t.has(e)||t.set(e,n)}return[...t.values()]}function C(e,t){return e.from.line-t.from.line||e.to.line-t.to.line}function w(e){for(let t=1;t<e.length;t+=1){let n=e[t-1],r=e[t];if(n!==void 0&&r!==void 0&&r.from.line<=n.to.line)throw Error(`Overlapping edit ranges ${n.from.line}-${n.to.line} and ${r.from.line}-${r.to.line}. Merge them and retry.`)}}function T(e){let t=s(e),n=t.endsWith(`
8
+ `),edits:r}}function v(e){if(e===`>> `)return`match`;if(e===` `)return`context`}function y(e){if(!Number.isSafeInteger(e)||e<1)throw Error(`Invalid tagged line number: ${String(e)}. Expected a positive safe integer.`)}function b(e,t){let n=g(e.from),r=g(e.to);if(n.line>r.line)throw Error(`Invalid edit range ${e.from} to ${e.to}: the starting line follows the ending line.`);if(r.line>t.length)throw Error(`Invalid edit range ${e.from} to ${e.to}: the file has ${t.length} lines.`);return x(n,t),x(r,t),{from:n,to:r,content:e.content??null,source:e}}function x(e,t){let n=t[e.line-1];if(n===void 0)throw Error(`Line ${e.line} is outside the current file.`);let r=o(n);if(r!==e.hash)throw Error(`Stale line anchor ${e.line}#${e.hash}. Current anchor is ${e.line}#${r}. Re-read the file and retry.`)}function S(e){let t=/* @__PURE__ */ new Map;for(let n of e){let e=JSON.stringify([n.from.line,n.from.hash,n.to.line,n.to.hash,n.content]);t.has(e)||t.set(e,n)}return[...t.values()]}function C(e,t){return e.from.line-t.from.line||e.to.line-t.to.line}function w(e){for(let t=1;t<e.length;t+=1){let n=e[t-1],r=e[t];if(n!==void 0&&r!==void 0&&r.from.line<=n.to.line)throw Error(`Overlapping edit ranges ${n.from.line}-${n.to.line} and ${r.from.line}-${r.to.line}. Merge them and retry.`)}}function T(e){let t=s(e),n=t.endsWith(`
9
9
  `)?t.slice(0,-1):t;return n===``?[]:n.split(`
10
10
  `)}exports.applyHashlineEdits=_,exports.formatFileHeader=d,exports.formatTaggedLine=p,exports.formatTaggedLines=m,exports.hashLine=o,exports.normalizeFileTag=u,exports.normalizeToLf=s,exports.parseFileHeader=f,exports.parseLineAnchor=g,exports.parseTaggedLine=h,exports.splitLines=l,exports.stripBom=c;
11
- //# sourceMappingURL=hashline.cjs.map
11
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/services/hashline/index.ts"],"sourcesContent":["import type {\n AppliedHashlineEdits,\n FileHeader,\n HashlineRangeInput,\n LineAnchor,\n ParsedTaggedLine,\n PreparedHashlineEdit,\n TaggedLineMarker,\n TaggedLinePrefix,\n} from '../../types/hashline';\n\nconst FNV_OFFSET_BASIS = 0xcbf29ce484222325n;\nconst FNV_PRIME = 0x100000001b3n;\nconst ANCHOR_SPACE = 26n * 26n * 26n;\nconst ASCII_A = 97;\nconst FILE_TAG_PATTERN = /^[A-Za-z0-9_-]{8}$/u;\nconst FILE_HEADER_PATTERN = /^@file (.+)#([A-Za-z0-9_-]{8})$/u;\nconst TAGGED_LINE_PATTERN = /^(>> | {3})?(\\d+)#[a-z]{3}\\|(.*)$/u;\nconst ANCHOR_PATTERN = /^\\s*[>+-]*\\s*(\\d+)\\s*[:#]\\s*([a-zA-Z]{3})(?:\\s*\\|.*)?\\s*$/u;\nconst ASCII_WHITESPACE = /[ \\t\\n\\r\\v\\f]/gu;\n\n/**\n * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.\n * Adapted from pulseaiclub/phi writetool under the MIT license.\n */\nexport function hashLine(line: string): string {\n const bytes = new TextEncoder().encode(line.replace(ASCII_WHITESPACE, ''));\n let hash = FNV_OFFSET_BASIS;\n for (const byte of bytes) {\n hash ^= BigInt(byte);\n hash = BigInt.asUintN(64, hash * FNV_PRIME);\n }\n\n let value = hash % ANCHOR_SPACE;\n const letters = Array.from({ length: 3 }, () => '');\n for (let index = letters.length - 1; index >= 0; index -= 1) {\n letters[index] = String.fromCharCode(ASCII_A + Number(value % 26n));\n value /= 26n;\n }\n return letters.join('');\n}\n\nexport function normalizeToLf(text: string): string {\n return text.replace(/\\r\\n/gu, '\\n').replace(/\\r/gu, '\\n');\n}\n\nexport function stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nexport function splitLines(text: string): string[] {\n return normalizeToLf(stripBom(text)).split('\\n');\n}\n\nexport function normalizeFileTag(value: string): string {\n const trimmed = value.trim();\n const hashIndex = trimmed.lastIndexOf('#');\n const tag = hashIndex === -1 ? trimmed : trimmed.slice(hashIndex + 1);\n if (!FILE_TAG_PATTERN.test(tag)) {\n throw new Error(`Invalid file hash: ${value}. Expected the eight-character tag from @file path#HASH.`);\n }\n return tag;\n}\n\nexport function formatFileHeader(path: string, tag: string): string {\n if (path.length === 0 || path.includes('\\n') || path.includes('\\r')) {\n throw new Error('Invalid file header path. Expected one non-empty path without line breaks.');\n }\n return `@file ${path}#${normalizeFileTag(tag)}`;\n}\n\nexport function parseFileHeader(value: string): FileHeader | undefined {\n const match = FILE_HEADER_PATTERN.exec(value);\n const path = match?.[1];\n const tag = match?.[2];\n return path === undefined || tag === undefined ? undefined : { path, tag };\n}\n\nexport function formatTaggedLine(\n line: string,\n lineNumber: number,\n prefix: TaggedLinePrefix = '',\n anchorSource = line,\n): string {\n assertLineNumber(lineNumber);\n return `${prefix}${lineNumber}#${hashLine(anchorSource)}|${line}`;\n}\n\nexport function formatTaggedLines(lines: readonly string[], firstLine = 1): string {\n return lines.map((line, index) => formatTaggedLine(line, firstLine + index)).join('\\n');\n}\n\nexport function parseTaggedLine(value: string): ParsedTaggedLine | undefined {\n const match = TAGGED_LINE_PATTERN.exec(value);\n const lineText = match?.[2];\n const content = match?.[3];\n if (lineText === undefined || content === undefined) return undefined;\n\n const line = Number.parseInt(lineText, 10);\n if (!Number.isSafeInteger(line) || line < 1) return undefined;\n\n const marker = taggedLineMarker(match?.[1]);\n return marker === undefined ? { content, line } : { content, line, marker };\n}\n\nexport function parseLineAnchor(value: string): LineAnchor {\n if (value.includes('\\n') || value.includes('\\r')) {\n throw new Error(`Invalid line anchor: ${value}. Expected one anchor such as 5#abc, not a pasted multiline block.`);\n }\n\n const match = ANCHOR_PATTERN.exec(value);\n if (!match) {\n throw new Error(\n `Invalid line anchor: ${value}. Expected one anchor such as 5#abc from the latest read or grep result.`,\n );\n }\n\n const line = Number.parseInt(match[1] ?? '', 10);\n const hash = match[2]?.toLowerCase();\n if (!Number.isSafeInteger(line) || line < 1 || hash === undefined) {\n throw new Error(`Invalid line anchor: ${value}. Expected a positive line number and three letters, such as 5#abc.`);\n }\n return { line, hash };\n}\n\nexport function applyHashlineEdits(\n originalContent: string,\n requestedEdits: readonly HashlineRangeInput[],\n): AppliedHashlineEdits {\n const lines = splitLines(originalContent);\n const validated = requestedEdits.map((edit) => validateEdit(edit, lines));\n const edits = deduplicateEdits(validated).sort(compareEdits);\n validateNoOverlaps(edits);\n\n const result = [...lines];\n for (const edit of [...edits].reverse()) {\n const deleteCount = edit.to.line - edit.from.line + 1;\n const replacements = edit.content === null || edit.content === '' ? [] : splitReplacement(edit.content);\n result.splice(edit.from.line - 1, deleteCount, ...replacements);\n }\n return { content: result.join('\\n'), edits };\n}\n\nfunction taggedLineMarker(prefix: string | undefined): TaggedLineMarker | undefined {\n if (prefix === '>> ') return 'match';\n if (prefix === ' ') return 'context';\n return undefined;\n}\n\nfunction assertLineNumber(line: number): void {\n if (!Number.isSafeInteger(line) || line < 1) {\n throw new Error(`Invalid tagged line number: ${String(line)}. Expected a positive safe integer.`);\n }\n}\n\nfunction validateEdit(edit: HashlineRangeInput, lines: readonly string[]): PreparedHashlineEdit {\n const from = parseLineAnchor(edit.from);\n const to = parseLineAnchor(edit.to);\n if (from.line > to.line) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the starting line follows the ending line.`);\n }\n if (to.line > lines.length) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the file has ${lines.length} lines.`);\n }\n\n validateAnchor(from, lines);\n validateAnchor(to, lines);\n return { from, to, content: edit.content ?? null, source: edit };\n}\n\nfunction validateAnchor(anchor: LineAnchor, lines: readonly string[]): void {\n const line = lines[anchor.line - 1];\n if (line === undefined) throw new Error(`Line ${anchor.line} is outside the current file.`);\n const currentHash = hashLine(line);\n if (currentHash !== anchor.hash) {\n throw new Error(\n `Stale line anchor ${anchor.line}#${anchor.hash}. Current anchor is ${anchor.line}#${currentHash}. Re-read the file and retry.`,\n );\n }\n}\n\nfunction deduplicateEdits(edits: readonly PreparedHashlineEdit[]): PreparedHashlineEdit[] {\n const unique = new Map<string, PreparedHashlineEdit>();\n for (const edit of edits) {\n const key = JSON.stringify([edit.from.line, edit.from.hash, edit.to.line, edit.to.hash, edit.content]);\n if (!unique.has(key)) unique.set(key, edit);\n }\n return [...unique.values()];\n}\n\nfunction compareEdits(left: PreparedHashlineEdit, right: PreparedHashlineEdit): number {\n return left.from.line - right.from.line || left.to.line - right.to.line;\n}\n\nfunction validateNoOverlaps(edits: readonly PreparedHashlineEdit[]): void {\n for (let index = 1; index < edits.length; index += 1) {\n const previous = edits[index - 1];\n const current = edits[index];\n if (previous !== undefined && current !== undefined && current.from.line <= previous.to.line) {\n throw new Error(\n `Overlapping edit ranges ${previous.from.line}-${previous.to.line} and ${current.from.line}-${current.to.line}. Merge them and retry.`,\n );\n }\n }\n}\n\nfunction splitReplacement(content: string): string[] {\n const normalized = normalizeToLf(content);\n const trimmed = normalized.endsWith('\\n') ? normalized.slice(0, -1) : normalized;\n return trimmed === '' ? [] : trimmed.split('\\n');\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "../../types/hashline.mjs";
2
+ //#region src/services/hashline/index.d.ts
3
+ /**
4
+ * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.
5
+ * Adapted from pulseaiclub/phi writetool under the MIT license.
6
+ */
7
+ export declare function hashLine(line: string): string;
8
+ export declare function normalizeToLf(text: string): string;
9
+ export declare function stripBom(text: string): string;
10
+ export declare function splitLines(text: string): string[];
11
+ export declare function normalizeFileTag(value: string): string;
12
+ export declare function formatFileHeader(path: string, tag: string): string;
13
+ export declare function parseFileHeader(value: string): FileHeader | undefined;
14
+ export declare function formatTaggedLine(line: string, lineNumber: number, prefix?: TaggedLinePrefix, anchorSource?: string): string;
15
+ export declare function formatTaggedLines(lines: readonly string[], firstLine?: number): string;
16
+ export declare function parseTaggedLine(value: string): ParsedTaggedLine | undefined;
17
+ export declare function parseLineAnchor(value: string): LineAnchor;
18
+ export declare function applyHashlineEdits(originalContent: string, requestedEdits: readonly HashlineRangeInput[]): AppliedHashlineEdits;
19
+ //#endregion
20
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/hashline/index.ts"],"mappings":";;;;;;wBAyBgB,SAAS;wBAiBT,cAAc;wBAId,SAAS;wBAIT,WAAW;wBAIX,iBAAiB;wBAUjB,iBAAiB,cAAc;wBAO/B,gBAAgB,gBAAgB;wBAOhC,iBACd,cACA,oBACA,SAAQ,kBACR;wBAMc,kBAAkB,0BAA0B;wBAI5C,gBAAgB,gBAAgB;wBAahC,gBAAgB,gBAAgB;wBAoBhC,mBACd,yBACA,yBAAyB,uBACxB"}
@@ -5,7 +5,7 @@ const e=26n*26n*26n,t=/^[A-Za-z0-9_-]{8}$/u,n=/^@file (.+)#([A-Za-z0-9_-]{8})$/u
5
5
  `)||e.includes(`\r`))throw Error(`Invalid file header path. Expected one non-empty path without line breaks.`);return`@file ${e}#${u(t)}`}function f(e){let t=n.exec(e),r=t?.[1],i=t?.[2];return r===void 0||i===void 0?void 0:{path:r,tag:i}}function p(e,t,n=``,r=e){return y(t),`${n}${t}#${o(r)}|${e}`}function m(e,t=1){return e.map((e,n)=>p(e,t+n)).join(`
6
6
  `)}function h(e){let t=r.exec(e),n=t?.[2],i=t?.[3];if(n===void 0||i===void 0)return;let a=Number.parseInt(n,10);if(!Number.isSafeInteger(a)||a<1)return;let o=v(t?.[1]);return o===void 0?{content:i,line:a}:{content:i,line:a,marker:o}}function g(e){if(e.includes(`
7
7
  `)||e.includes(`\r`))throw Error(`Invalid line anchor: ${e}. Expected one anchor such as 5#abc, not a pasted multiline block.`);let t=i.exec(e);if(!t)throw Error(`Invalid line anchor: ${e}. Expected one anchor such as 5#abc from the latest read or grep result.`);let n=Number.parseInt(t[1]??``,10),r=t[2]?.toLowerCase();if(!Number.isSafeInteger(n)||n<1||r===void 0)throw Error(`Invalid line anchor: ${e}. Expected a positive line number and three letters, such as 5#abc.`);return{line:n,hash:r}}function _(e,t){let n=l(e),r=S(t.map(e=>b(e,n))).sort(C);w(r);let i=[...n];for(let e of[...r].reverse()){let t=e.to.line-e.from.line+1,n=e.content===null||e.content===``?[]:T(e.content);i.splice(e.from.line-1,t,...n)}return{content:i.join(`
8
- `),edits:r}}function v(e){if(e===`>> `)return`match`;if(e===` `)return`context`}function y(e){if(!Number.isSafeInteger(e)||e<1)throw Error(`Invalid tagged line number: ${String(e)}. Expected a positive safe integer.`)}function b(e,t){let n=g(e.from),r=g(e.to);if(n.line>r.line)throw Error(`Invalid edit range ${e.from} to ${e.to}: the starting line follows the ending line.`);if(r.line>t.length)throw Error(`Invalid edit range ${e.from} to ${e.to}: the file has ${t.length} lines.`);return x(n,t),x(r,t),{from:n,to:r,content:e.content??null,source:e}}function x(e,t){let n=t[e.line-1];if(n===void 0)throw Error(`Line ${e.line} is outside the current file.`);let r=o(n);if(r!==e.hash)throw Error(`Stale line anchor ${e.line}#${e.hash}. Current anchor is ${e.line}#${r}. Re-read the file and retry.`)}function S(e){let t=new Map;for(let n of e){let e=JSON.stringify([n.from.line,n.from.hash,n.to.line,n.to.hash,n.content]);t.has(e)||t.set(e,n)}return[...t.values()]}function C(e,t){return e.from.line-t.from.line||e.to.line-t.to.line}function w(e){for(let t=1;t<e.length;t+=1){let n=e[t-1],r=e[t];if(n!==void 0&&r!==void 0&&r.from.line<=n.to.line)throw Error(`Overlapping edit ranges ${n.from.line}-${n.to.line} and ${r.from.line}-${r.to.line}. Merge them and retry.`)}}function T(e){let t=s(e),n=t.endsWith(`
8
+ `),edits:r}}function v(e){if(e===`>> `)return`match`;if(e===` `)return`context`}function y(e){if(!Number.isSafeInteger(e)||e<1)throw Error(`Invalid tagged line number: ${String(e)}. Expected a positive safe integer.`)}function b(e,t){let n=g(e.from),r=g(e.to);if(n.line>r.line)throw Error(`Invalid edit range ${e.from} to ${e.to}: the starting line follows the ending line.`);if(r.line>t.length)throw Error(`Invalid edit range ${e.from} to ${e.to}: the file has ${t.length} lines.`);return x(n,t),x(r,t),{from:n,to:r,content:e.content??null,source:e}}function x(e,t){let n=t[e.line-1];if(n===void 0)throw Error(`Line ${e.line} is outside the current file.`);let r=o(n);if(r!==e.hash)throw Error(`Stale line anchor ${e.line}#${e.hash}. Current anchor is ${e.line}#${r}. Re-read the file and retry.`)}function S(e){let t=/* @__PURE__ */ new Map;for(let n of e){let e=JSON.stringify([n.from.line,n.from.hash,n.to.line,n.to.hash,n.content]);t.has(e)||t.set(e,n)}return[...t.values()]}function C(e,t){return e.from.line-t.from.line||e.to.line-t.to.line}function w(e){for(let t=1;t<e.length;t+=1){let n=e[t-1],r=e[t];if(n!==void 0&&r!==void 0&&r.from.line<=n.to.line)throw Error(`Overlapping edit ranges ${n.from.line}-${n.to.line} and ${r.from.line}-${r.to.line}. Merge them and retry.`)}}function T(e){let t=s(e),n=t.endsWith(`
9
9
  `)?t.slice(0,-1):t;return n===``?[]:n.split(`
10
10
  `)}export{_ as applyHashlineEdits,d as formatFileHeader,p as formatTaggedLine,m as formatTaggedLines,o as hashLine,u as normalizeFileTag,s as normalizeToLf,f as parseFileHeader,g as parseLineAnchor,h as parseTaggedLine,l as splitLines,c as stripBom};
11
- //# sourceMappingURL=hashline.mjs.map
11
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/services/hashline/index.ts"],"sourcesContent":["import type {\n AppliedHashlineEdits,\n FileHeader,\n HashlineRangeInput,\n LineAnchor,\n ParsedTaggedLine,\n PreparedHashlineEdit,\n TaggedLineMarker,\n TaggedLinePrefix,\n} from '../../types/hashline';\n\nconst FNV_OFFSET_BASIS = 0xcbf29ce484222325n;\nconst FNV_PRIME = 0x100000001b3n;\nconst ANCHOR_SPACE = 26n * 26n * 26n;\nconst ASCII_A = 97;\nconst FILE_TAG_PATTERN = /^[A-Za-z0-9_-]{8}$/u;\nconst FILE_HEADER_PATTERN = /^@file (.+)#([A-Za-z0-9_-]{8})$/u;\nconst TAGGED_LINE_PATTERN = /^(>> | {3})?(\\d+)#[a-z]{3}\\|(.*)$/u;\nconst ANCHOR_PATTERN = /^\\s*[>+-]*\\s*(\\d+)\\s*[:#]\\s*([a-zA-Z]{3})(?:\\s*\\|.*)?\\s*$/u;\nconst ASCII_WHITESPACE = /[ \\t\\n\\r\\v\\f]/gu;\n\n/**\n * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.\n * Adapted from pulseaiclub/phi writetool under the MIT license.\n */\nexport function hashLine(line: string): string {\n const bytes = new TextEncoder().encode(line.replace(ASCII_WHITESPACE, ''));\n let hash = FNV_OFFSET_BASIS;\n for (const byte of bytes) {\n hash ^= BigInt(byte);\n hash = BigInt.asUintN(64, hash * FNV_PRIME);\n }\n\n let value = hash % ANCHOR_SPACE;\n const letters = Array.from({ length: 3 }, () => '');\n for (let index = letters.length - 1; index >= 0; index -= 1) {\n letters[index] = String.fromCharCode(ASCII_A + Number(value % 26n));\n value /= 26n;\n }\n return letters.join('');\n}\n\nexport function normalizeToLf(text: string): string {\n return text.replace(/\\r\\n/gu, '\\n').replace(/\\r/gu, '\\n');\n}\n\nexport function stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nexport function splitLines(text: string): string[] {\n return normalizeToLf(stripBom(text)).split('\\n');\n}\n\nexport function normalizeFileTag(value: string): string {\n const trimmed = value.trim();\n const hashIndex = trimmed.lastIndexOf('#');\n const tag = hashIndex === -1 ? trimmed : trimmed.slice(hashIndex + 1);\n if (!FILE_TAG_PATTERN.test(tag)) {\n throw new Error(`Invalid file hash: ${value}. Expected the eight-character tag from @file path#HASH.`);\n }\n return tag;\n}\n\nexport function formatFileHeader(path: string, tag: string): string {\n if (path.length === 0 || path.includes('\\n') || path.includes('\\r')) {\n throw new Error('Invalid file header path. Expected one non-empty path without line breaks.');\n }\n return `@file ${path}#${normalizeFileTag(tag)}`;\n}\n\nexport function parseFileHeader(value: string): FileHeader | undefined {\n const match = FILE_HEADER_PATTERN.exec(value);\n const path = match?.[1];\n const tag = match?.[2];\n return path === undefined || tag === undefined ? undefined : { path, tag };\n}\n\nexport function formatTaggedLine(\n line: string,\n lineNumber: number,\n prefix: TaggedLinePrefix = '',\n anchorSource = line,\n): string {\n assertLineNumber(lineNumber);\n return `${prefix}${lineNumber}#${hashLine(anchorSource)}|${line}`;\n}\n\nexport function formatTaggedLines(lines: readonly string[], firstLine = 1): string {\n return lines.map((line, index) => formatTaggedLine(line, firstLine + index)).join('\\n');\n}\n\nexport function parseTaggedLine(value: string): ParsedTaggedLine | undefined {\n const match = TAGGED_LINE_PATTERN.exec(value);\n const lineText = match?.[2];\n const content = match?.[3];\n if (lineText === undefined || content === undefined) return undefined;\n\n const line = Number.parseInt(lineText, 10);\n if (!Number.isSafeInteger(line) || line < 1) return undefined;\n\n const marker = taggedLineMarker(match?.[1]);\n return marker === undefined ? { content, line } : { content, line, marker };\n}\n\nexport function parseLineAnchor(value: string): LineAnchor {\n if (value.includes('\\n') || value.includes('\\r')) {\n throw new Error(`Invalid line anchor: ${value}. Expected one anchor such as 5#abc, not a pasted multiline block.`);\n }\n\n const match = ANCHOR_PATTERN.exec(value);\n if (!match) {\n throw new Error(\n `Invalid line anchor: ${value}. Expected one anchor such as 5#abc from the latest read or grep result.`,\n );\n }\n\n const line = Number.parseInt(match[1] ?? '', 10);\n const hash = match[2]?.toLowerCase();\n if (!Number.isSafeInteger(line) || line < 1 || hash === undefined) {\n throw new Error(`Invalid line anchor: ${value}. Expected a positive line number and three letters, such as 5#abc.`);\n }\n return { line, hash };\n}\n\nexport function applyHashlineEdits(\n originalContent: string,\n requestedEdits: readonly HashlineRangeInput[],\n): AppliedHashlineEdits {\n const lines = splitLines(originalContent);\n const validated = requestedEdits.map((edit) => validateEdit(edit, lines));\n const edits = deduplicateEdits(validated).sort(compareEdits);\n validateNoOverlaps(edits);\n\n const result = [...lines];\n for (const edit of [...edits].reverse()) {\n const deleteCount = edit.to.line - edit.from.line + 1;\n const replacements = edit.content === null || edit.content === '' ? [] : splitReplacement(edit.content);\n result.splice(edit.from.line - 1, deleteCount, ...replacements);\n }\n return { content: result.join('\\n'), edits };\n}\n\nfunction taggedLineMarker(prefix: string | undefined): TaggedLineMarker | undefined {\n if (prefix === '>> ') return 'match';\n if (prefix === ' ') return 'context';\n return undefined;\n}\n\nfunction assertLineNumber(line: number): void {\n if (!Number.isSafeInteger(line) || line < 1) {\n throw new Error(`Invalid tagged line number: ${String(line)}. Expected a positive safe integer.`);\n }\n}\n\nfunction validateEdit(edit: HashlineRangeInput, lines: readonly string[]): PreparedHashlineEdit {\n const from = parseLineAnchor(edit.from);\n const to = parseLineAnchor(edit.to);\n if (from.line > to.line) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the starting line follows the ending line.`);\n }\n if (to.line > lines.length) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the file has ${lines.length} lines.`);\n }\n\n validateAnchor(from, lines);\n validateAnchor(to, lines);\n return { from, to, content: edit.content ?? null, source: edit };\n}\n\nfunction validateAnchor(anchor: LineAnchor, lines: readonly string[]): void {\n const line = lines[anchor.line - 1];\n if (line === undefined) throw new Error(`Line ${anchor.line} is outside the current file.`);\n const currentHash = hashLine(line);\n if (currentHash !== anchor.hash) {\n throw new Error(\n `Stale line anchor ${anchor.line}#${anchor.hash}. Current anchor is ${anchor.line}#${currentHash}. Re-read the file and retry.`,\n );\n }\n}\n\nfunction deduplicateEdits(edits: readonly PreparedHashlineEdit[]): PreparedHashlineEdit[] {\n const unique = new Map<string, PreparedHashlineEdit>();\n for (const edit of edits) {\n const key = JSON.stringify([edit.from.line, edit.from.hash, edit.to.line, edit.to.hash, edit.content]);\n if (!unique.has(key)) unique.set(key, edit);\n }\n return [...unique.values()];\n}\n\nfunction compareEdits(left: PreparedHashlineEdit, right: PreparedHashlineEdit): number {\n return left.from.line - right.from.line || left.to.line - right.to.line;\n}\n\nfunction validateNoOverlaps(edits: readonly PreparedHashlineEdit[]): void {\n for (let index = 1; index < edits.length; index += 1) {\n const previous = edits[index - 1];\n const current = edits[index];\n if (previous !== undefined && current !== undefined && current.from.line <= previous.to.line) {\n throw new Error(\n `Overlapping edit ranges ${previous.from.line}-${previous.to.line} and ${current.from.line}-${current.to.line}. Merge them and retry.`,\n );\n }\n }\n}\n\nfunction splitReplacement(content: string): string[] {\n const normalized = normalizeToLf(content);\n const trimmed = normalized.endsWith('\\n') ? normalized.slice(0, -1) : normalized;\n return trimmed === '' ? [] : trimmed.split('\\n');\n}\n"],"mappings":"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"}
@@ -1,34 +1,33 @@
1
1
  //#region src/types/hashline.d.ts
2
- interface HashlineRangeInput {
2
+ export interface HashlineRangeInput {
3
3
  readonly from: string;
4
4
  readonly to: string;
5
5
  readonly content?: string | null;
6
6
  }
7
- interface LineAnchor {
7
+ export interface LineAnchor {
8
8
  readonly line: number;
9
9
  readonly hash: string;
10
10
  }
11
- interface PreparedHashlineEdit {
11
+ export interface PreparedHashlineEdit {
12
12
  readonly from: LineAnchor;
13
13
  readonly to: LineAnchor;
14
14
  readonly content: string | null;
15
15
  readonly source: HashlineRangeInput;
16
16
  }
17
- interface AppliedHashlineEdits {
17
+ export interface AppliedHashlineEdits {
18
18
  readonly content: string;
19
19
  readonly edits: readonly PreparedHashlineEdit[];
20
20
  }
21
- interface FileHeader {
21
+ export interface FileHeader {
22
22
  readonly path: string;
23
23
  readonly tag: string;
24
24
  }
25
- type TaggedLineMarker = 'match' | 'context';
26
- interface ParsedTaggedLine {
25
+ export type TaggedLineMarker = 'match' | 'context';
26
+ export interface ParsedTaggedLine {
27
27
  readonly content: string;
28
28
  readonly line: number;
29
29
  readonly marker?: TaggedLineMarker;
30
30
  }
31
- type TaggedLinePrefix = '' | '>> ' | ' ';
31
+ export type TaggedLinePrefix = '' | '>> ' | ' ';
32
32
  //#endregion
33
- export { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, PreparedHashlineEdit, TaggedLineMarker, TaggedLinePrefix };
34
33
  //# sourceMappingURL=hashline.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hashline.d.mts","names":[],"sources":["../../src/types/hashline.ts"],"mappings":";UAAiB;WACN;WACA;WACA;;UAGM;WACN;WACA;;UAGM;WACN,MAAM;WACN,IAAI;WACJ;WACA,QAAQ;;UAGF;WACN;WACA,gBAAgB;;UAGV;WACN;WACA;;KAGC;UAEK;WACN;WACA;WACA,SAAS;;KAGR"}
1
+ {"version":3,"file":"hashline.d.mts","names":[],"sources":["../../src/types/hashline.ts"],"mappings":";iBAAiB;WACN;WACA;WACA;;iBAGM;WACN;WACA;;iBAGM;WACN,MAAM;WACN,IAAI;WACJ;WACA,QAAQ;;iBAGF;WACN;WACA,gBAAgB;;iBAGV;WACN;WACA;;YAGC;iBAEK;WACN;WACA;WACA,SAAS;;YAGR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-hashline",
3
- "version": "0.0.1-alpha.6",
3
+ "version": "0.0.1-alpha.60",
4
4
  "description": "Shared snapshot-bound file tags and line anchors for DoomPi tools.",
5
5
  "keywords": [
6
6
  "ai",
@@ -13,8 +13,16 @@
13
13
  "typescript"
14
14
  ],
15
15
  "homepage": "https://agimon.ai",
16
+ "bugs": {
17
+ "url": "https://github.com/AgiFlow/doompi/issues"
18
+ },
16
19
  "license": "MIT",
17
20
  "author": "Vuong Ngo",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/AgiFlow/doompi.git",
24
+ "directory": "packages/utils/doompi-hashline"
25
+ },
18
26
  "files": [
19
27
  "dist"
20
28
  ],
@@ -39,11 +47,11 @@
39
47
  "access": "public"
40
48
  },
41
49
  "devDependencies": {
42
- "@types/node": "26.2.0",
43
- "@vitest/coverage-v8": "4.1.11",
44
- "tsdown": "0.22.14",
50
+ "@types/node": "26.6.2",
51
+ "@vitest/coverage-v8": "5.0.1",
52
+ "tsdown": "0.23.0",
45
53
  "typescript": "7.0.2",
46
- "vitest": "4.1.11"
54
+ "vitest": "5.0.1"
47
55
  },
48
56
  "engines": {
49
57
  "node": ">=22.19.0"
@@ -1 +0,0 @@
1
- {"version":3,"file":"files.cjs","names":["createHash","access","constants","homedir","sep","resolve","fileURLToPath","isAbsolute","relative"],"sources":["../../../src/adapters/node/files.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { access } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/gu;\nconst NARROW_NO_BREAK_SPACE = '\\u202f';\n\nexport function computeFileTag(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('base64url').slice(0, 8);\n}\n\nexport function decodeUtf8(bytes: Uint8Array, path: string): string {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch (error) {\n throw new Error(`Cannot use hashline tools on ${path}: the file is not valid UTF-8.`, { cause: error });\n }\n}\n\n/** Return whether the current process can edit an existing file. */\nexport async function isWritableFile(path: string): Promise<boolean> {\n return access(path, constants.W_OK).then(\n () => true,\n () => false,\n );\n}\n\nexport function resolveInputPath(input: string, cwd: string): string {\n let path = input.replace(UNICODE_SPACES, ' ');\n if (path.startsWith('@')) path = path.slice(1);\n if (process.platform === 'win32') path = normalizeWindowsShellPath(path);\n if (path === '~') path = homedir();\n else if (path.startsWith(`~${sep}`)) path = resolve(homedir(), path.slice(2));\n else if (path.startsWith('file://')) path = fileURLToPath(path);\n return isAbsolute(path) ? resolve(path) : resolve(cwd, path);\n}\n\n/** Resolve the same existing-file variants as Pi's native read tool. */\nexport async function resolveReadInputPath(input: string, cwd: string): Promise<string> {\n const resolved = resolveInputPath(input, cwd);\n if (await pathExists(resolved)) return resolved;\n\n const variants = [\n resolved.replace(/ (AM|PM)\\./giu, `${NARROW_NO_BREAK_SPACE}$1.`),\n resolved.normalize('NFD'),\n resolved.replaceAll(\"'\", '\\u2019'),\n resolved.normalize('NFD').replaceAll(\"'\", '\\u2019'),\n ];\n for (const candidate of variants) {\n if (candidate !== resolved && (await pathExists(candidate))) return candidate;\n }\n return resolved;\n}\n\nexport function displayPath(absolutePath: string, cwd: string): string {\n const path = relative(cwd, absolutePath);\n const display =\n path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path) ? path : absolutePath;\n const portable = display.split(sep).join('/');\n return portable === '~' || portable.startsWith('~/') ? `./${portable}` : portable;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n return access(path).then(\n () => true,\n () => false,\n );\n}\n\nfunction normalizeWindowsShellPath(path: string): string {\n if (!path.startsWith('/') || path.startsWith('//') || path.includes('\\\\')) return path;\n const match = /^\\/(?:mnt\\/|cygdrive\\/)?([a-z])(?:\\/(.*))?$/iu.exec(path);\n if (!match?.[1]) return path;\n const suffix = match[2]?.replaceAll('/', '\\\\');\n return `${match[1].toUpperCase()}:\\\\${suffix ?? ''}`;\n}\n"],"mappings":"kJAOA,MAAM,EAAiB,4CAGvB,SAAgB,EAAe,EAA2B,CACxD,OAAA,EAAOA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,EAAG,CAAC,CAC1E,CAEA,SAAgB,EAAW,EAAmB,EAAsB,CAClE,GAAI,CACF,OAAO,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,CAAC,CAAC,OAAO,CAAK,CAC/D,OAAS,EAAO,CACd,MAAU,MAAM,gCAAgC,EAAK,gCAAiC,CAAE,MAAO,CAAM,CAAC,CACxG,CACF,CAGA,eAAsB,EAAe,EAAgC,CACnE,OAAA,EAAOC,EAAAA,OAAAA,CAAO,EAAMC,EAAAA,UAAU,IAAI,CAAC,CAAC,SAC5B,OACA,EACR,CACF,CAEA,SAAgB,EAAiB,EAAe,EAAqB,CACnE,IAAI,EAAO,EAAM,QAAQ,EAAgB,GAAG,EAM5C,OALI,EAAK,WAAW,GAAG,IAAG,EAAO,EAAK,MAAM,CAAC,GACzC,QAAQ,WAAa,UAAS,EAAO,EAA0B,CAAI,GACnE,IAAS,IAAK,GAAA,EAAOC,EAAAA,QAAAA,CAAQ,EACxB,EAAK,WAAW,IAAIC,EAAAA,KAAK,EAAG,GAAA,EAAOC,EAAAA,QAAAA,EAAAA,EAAQF,EAAAA,QAAAA,CAAQ,EAAG,EAAK,MAAM,CAAC,CAAC,EACnE,EAAK,WAAW,SAAS,IAAG,GAAA,EAAOG,EAAAA,cAAAA,CAAc,CAAI,IAC9D,EAAOC,EAAAA,WAAAA,CAAW,CAAI,GAAA,EAAIF,EAAAA,QAAAA,CAAQ,CAAI,GAAA,EAAIA,EAAAA,QAAAA,CAAQ,EAAK,CAAI,CAC7D,CAGA,eAAsB,EAAqB,EAAe,EAA8B,CACtF,IAAM,EAAW,EAAiB,EAAO,CAAG,EAC5C,GAAI,MAAM,EAAW,CAAQ,EAAG,OAAO,EAEvC,IAAM,EAAW,CACf,EAAS,QAAQ,gBAAiB,MAA6B,EAC/D,EAAS,UAAU,KAAK,EACxB,EAAS,WAAW,IAAK,GAAQ,EACjC,EAAS,UAAU,KAAK,CAAC,CAAC,WAAW,IAAK,GAAQ,CACpD,EACA,IAAK,IAAM,KAAa,EACtB,GAAI,IAAc,GAAa,MAAM,EAAW,CAAS,EAAI,OAAO,EAEtE,OAAO,CACT,CAEA,SAAgB,EAAY,EAAsB,EAAqB,CACrE,IAAM,GAAA,EAAOG,EAAAA,SAAAA,CAAS,EAAK,CAAY,EAGjC,GADJ,IAAS,IAAM,IAAS,MAAQ,CAAC,EAAK,WAAW,KAAKJ,EAAAA,KAAK,GAAK,EAAA,EAACG,EAAAA,WAAAA,CAAW,CAAI,EAAI,EAAO,EAAA,CACpE,MAAMH,EAAAA,GAAG,CAAC,CAAC,KAAK,GAAG,EAC5C,OAAO,IAAa,KAAO,EAAS,WAAW,IAAI,EAAI,KAAK,IAAa,CAC3E,CAEA,eAAe,EAAW,EAAgC,CACxD,OAAA,EAAOH,EAAAA,OAAAA,CAAO,CAAI,CAAC,CAAC,SACZ,OACA,EACR,CACF,CAEA,SAAS,EAA0B,EAAsB,CACvD,GAAI,CAAC,EAAK,WAAW,GAAG,GAAK,EAAK,WAAW,IAAI,GAAK,EAAK,SAAS,IAAI,EAAG,OAAO,EAClF,IAAM,EAAQ,gDAAgD,KAAK,CAAI,EACvE,GAAI,CAAC,IAAQ,GAAI,OAAO,EACxB,IAAM,EAAS,EAAM,EAAE,EAAE,WAAW,IAAK,IAAI,EAC7C,MAAO,GAAG,EAAM,EAAE,CAAC,YAAY,EAAE,KAAK,GAAU,IAClD"}
@@ -1,12 +0,0 @@
1
- //#region src/adapters/node/files.d.ts
2
- declare function computeFileTag(bytes: Uint8Array): string;
3
- declare function decodeUtf8(bytes: Uint8Array, path: string): string;
4
- /** Return whether the current process can edit an existing file. */
5
- declare function isWritableFile(path: string): Promise<boolean>;
6
- declare function resolveInputPath(input: string, cwd: string): string;
7
- /** Resolve the same existing-file variants as Pi's native read tool. */
8
- declare function resolveReadInputPath(input: string, cwd: string): Promise<string>;
9
- declare function displayPath(absolutePath: string, cwd: string): string;
10
- //#endregion
11
- export { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath };
12
- //# sourceMappingURL=files.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"files.d.cts","names":[],"sources":["../../../src/adapters/node/files.ts"],"mappings":";iBAUgB,eAAe,OAAO;iBAItB,WAAW,OAAO,YAAY;;iBASxB,eAAe,eAAe;iBAOpC,iBAAiB,eAAe;;iBAW1B,qBAAqB,eAAe,cAAc;iBAgBxD,YAAY,sBAAsB"}
@@ -1,12 +0,0 @@
1
- //#region src/adapters/node/files.d.ts
2
- declare function computeFileTag(bytes: Uint8Array): string;
3
- declare function decodeUtf8(bytes: Uint8Array, path: string): string;
4
- /** Return whether the current process can edit an existing file. */
5
- declare function isWritableFile(path: string): Promise<boolean>;
6
- declare function resolveInputPath(input: string, cwd: string): string;
7
- /** Resolve the same existing-file variants as Pi's native read tool. */
8
- declare function resolveReadInputPath(input: string, cwd: string): Promise<string>;
9
- declare function displayPath(absolutePath: string, cwd: string): string;
10
- //#endregion
11
- export { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath };
12
- //# sourceMappingURL=files.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"files.d.mts","names":[],"sources":["../../../src/adapters/node/files.ts"],"mappings":";iBAUgB,eAAe,OAAO;iBAItB,WAAW,OAAO,YAAY;;iBASxB,eAAe,eAAe;iBAOpC,iBAAiB,eAAe;;iBAW1B,qBAAqB,eAAe,cAAc;iBAgBxD,YAAY,sBAAsB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"files.mjs","names":[],"sources":["../../../src/adapters/node/files.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { access } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/gu;\nconst NARROW_NO_BREAK_SPACE = '\\u202f';\n\nexport function computeFileTag(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('base64url').slice(0, 8);\n}\n\nexport function decodeUtf8(bytes: Uint8Array, path: string): string {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch (error) {\n throw new Error(`Cannot use hashline tools on ${path}: the file is not valid UTF-8.`, { cause: error });\n }\n}\n\n/** Return whether the current process can edit an existing file. */\nexport async function isWritableFile(path: string): Promise<boolean> {\n return access(path, constants.W_OK).then(\n () => true,\n () => false,\n );\n}\n\nexport function resolveInputPath(input: string, cwd: string): string {\n let path = input.replace(UNICODE_SPACES, ' ');\n if (path.startsWith('@')) path = path.slice(1);\n if (process.platform === 'win32') path = normalizeWindowsShellPath(path);\n if (path === '~') path = homedir();\n else if (path.startsWith(`~${sep}`)) path = resolve(homedir(), path.slice(2));\n else if (path.startsWith('file://')) path = fileURLToPath(path);\n return isAbsolute(path) ? resolve(path) : resolve(cwd, path);\n}\n\n/** Resolve the same existing-file variants as Pi's native read tool. */\nexport async function resolveReadInputPath(input: string, cwd: string): Promise<string> {\n const resolved = resolveInputPath(input, cwd);\n if (await pathExists(resolved)) return resolved;\n\n const variants = [\n resolved.replace(/ (AM|PM)\\./giu, `${NARROW_NO_BREAK_SPACE}$1.`),\n resolved.normalize('NFD'),\n resolved.replaceAll(\"'\", '\\u2019'),\n resolved.normalize('NFD').replaceAll(\"'\", '\\u2019'),\n ];\n for (const candidate of variants) {\n if (candidate !== resolved && (await pathExists(candidate))) return candidate;\n }\n return resolved;\n}\n\nexport function displayPath(absolutePath: string, cwd: string): string {\n const path = relative(cwd, absolutePath);\n const display =\n path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path) ? path : absolutePath;\n const portable = display.split(sep).join('/');\n return portable === '~' || portable.startsWith('~/') ? `./${portable}` : portable;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n return access(path).then(\n () => true,\n () => false,\n );\n}\n\nfunction normalizeWindowsShellPath(path: string): string {\n if (!path.startsWith('/') || path.startsWith('//') || path.includes('\\\\')) return path;\n const match = /^\\/(?:mnt\\/|cygdrive\\/)?([a-z])(?:\\/(.*))?$/iu.exec(path);\n if (!match?.[1]) return path;\n const suffix = match[2]?.replaceAll('/', '\\\\');\n return `${match[1].toUpperCase()}:\\\\${suffix ?? ''}`;\n}\n"],"mappings":"6QAOA,MAAM,EAAiB,4CAGvB,SAAgB,EAAe,EAA2B,CACxD,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,EAAG,CAAC,CAC1E,CAEA,SAAgB,EAAW,EAAmB,EAAsB,CAClE,GAAI,CACF,OAAO,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,CAAC,CAAC,OAAO,CAAK,CAC/D,OAAS,EAAO,CACd,MAAU,MAAM,gCAAgC,EAAK,gCAAiC,CAAE,MAAO,CAAM,CAAC,CACxG,CACF,CAGA,eAAsB,EAAe,EAAgC,CACnE,OAAO,EAAO,EAAM,EAAU,IAAI,CAAC,CAAC,SAC5B,OACA,EACR,CACF,CAEA,SAAgB,EAAiB,EAAe,EAAqB,CACnE,IAAI,EAAO,EAAM,QAAQ,EAAgB,GAAG,EAM5C,OALI,EAAK,WAAW,GAAG,IAAG,EAAO,EAAK,MAAM,CAAC,GACzC,QAAQ,WAAa,UAAS,EAAO,EAA0B,CAAI,GACnE,IAAS,IAAK,EAAO,EAAQ,EACxB,EAAK,WAAW,IAAI,GAAK,EAAG,EAAO,EAAQ,EAAQ,EAAG,EAAK,MAAM,CAAC,CAAC,EACnE,EAAK,WAAW,SAAS,IAAG,EAAO,EAAc,CAAI,GACvD,EAAW,CAAI,EAAI,EAAQ,CAAI,EAAI,EAAQ,EAAK,CAAI,CAC7D,CAGA,eAAsB,EAAqB,EAAe,EAA8B,CACtF,IAAM,EAAW,EAAiB,EAAO,CAAG,EAC5C,GAAI,MAAM,EAAW,CAAQ,EAAG,OAAO,EAEvC,IAAM,EAAW,CACf,EAAS,QAAQ,gBAAiB,MAA6B,EAC/D,EAAS,UAAU,KAAK,EACxB,EAAS,WAAW,IAAK,GAAQ,EACjC,EAAS,UAAU,KAAK,CAAC,CAAC,WAAW,IAAK,GAAQ,CACpD,EACA,IAAK,IAAM,KAAa,EACtB,GAAI,IAAc,GAAa,MAAM,EAAW,CAAS,EAAI,OAAO,EAEtE,OAAO,CACT,CAEA,SAAgB,EAAY,EAAsB,EAAqB,CACrE,IAAM,EAAO,EAAS,EAAK,CAAY,EAGjC,GADJ,IAAS,IAAM,IAAS,MAAQ,CAAC,EAAK,WAAW,KAAK,GAAK,GAAK,CAAC,EAAW,CAAI,EAAI,EAAO,EAAA,CACpE,MAAM,CAAG,CAAC,CAAC,KAAK,GAAG,EAC5C,OAAO,IAAa,KAAO,EAAS,WAAW,IAAI,EAAI,KAAK,IAAa,CAC3E,CAEA,eAAe,EAAW,EAAgC,CACxD,OAAO,EAAO,CAAI,CAAC,CAAC,SACZ,OACA,EACR,CACF,CAEA,SAAS,EAA0B,EAAsB,CACvD,GAAI,CAAC,EAAK,WAAW,GAAG,GAAK,EAAK,WAAW,IAAI,GAAK,EAAK,SAAS,IAAI,EAAG,OAAO,EAClF,IAAM,EAAQ,gDAAgD,KAAK,CAAI,EACvE,GAAI,CAAC,IAAQ,GAAI,OAAO,EACxB,IAAM,EAAS,EAAM,EAAE,EAAE,WAAW,IAAK,IAAI,EAC7C,MAAO,GAAG,EAAM,EAAE,CAAC,YAAY,EAAE,KAAK,GAAU,IAClD"}
package/dist/files.d.cts DELETED
@@ -1,2 +0,0 @@
1
- import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath } from "./adapters/node/files.cjs";
2
- export { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath };
package/dist/index.d.cts DELETED
@@ -1,3 +0,0 @@
1
- import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, PreparedHashlineEdit, TaggedLineMarker, TaggedLinePrefix } from "./types/hashline.cjs";
2
- import { applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom } from "./services/hashline.cjs";
3
- export { type AppliedHashlineEdits, type FileHeader, type HashlineRangeInput, type LineAnchor, type ParsedTaggedLine, type PreparedHashlineEdit, type TaggedLineMarker, type TaggedLinePrefix, applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom };
@@ -1 +0,0 @@
1
- {"version":3,"file":"hashline.cjs","names":[],"sources":["../../src/services/hashline.ts"],"sourcesContent":["import type {\n AppliedHashlineEdits,\n FileHeader,\n HashlineRangeInput,\n LineAnchor,\n ParsedTaggedLine,\n PreparedHashlineEdit,\n TaggedLineMarker,\n TaggedLinePrefix,\n} from '../types/hashline.ts';\n\nconst FNV_OFFSET_BASIS = 0xcbf29ce484222325n;\nconst FNV_PRIME = 0x100000001b3n;\nconst ANCHOR_SPACE = 26n * 26n * 26n;\nconst ASCII_A = 97;\nconst FILE_TAG_PATTERN = /^[A-Za-z0-9_-]{8}$/u;\nconst FILE_HEADER_PATTERN = /^@file (.+)#([A-Za-z0-9_-]{8})$/u;\nconst TAGGED_LINE_PATTERN = /^(>> | {3})?(\\d+)#[a-z]{3}\\|(.*)$/u;\nconst ANCHOR_PATTERN = /^\\s*[>+-]*\\s*(\\d+)\\s*[:#]\\s*([a-zA-Z]{3})(?:\\s*\\|.*)?\\s*$/u;\nconst ASCII_WHITESPACE = /[ \\t\\n\\r\\v\\f]/gu;\n\n/**\n * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.\n * Adapted from pulseaiclub/phi writetool under the MIT license.\n */\nexport function hashLine(line: string): string {\n const bytes = new TextEncoder().encode(line.replace(ASCII_WHITESPACE, ''));\n let hash = FNV_OFFSET_BASIS;\n for (const byte of bytes) {\n hash ^= BigInt(byte);\n hash = BigInt.asUintN(64, hash * FNV_PRIME);\n }\n\n let value = hash % ANCHOR_SPACE;\n const letters = Array.from({ length: 3 }, () => '');\n for (let index = letters.length - 1; index >= 0; index -= 1) {\n letters[index] = String.fromCharCode(ASCII_A + Number(value % 26n));\n value /= 26n;\n }\n return letters.join('');\n}\n\nexport function normalizeToLf(text: string): string {\n return text.replace(/\\r\\n/gu, '\\n').replace(/\\r/gu, '\\n');\n}\n\nexport function stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nexport function splitLines(text: string): string[] {\n return normalizeToLf(stripBom(text)).split('\\n');\n}\n\nexport function normalizeFileTag(value: string): string {\n const trimmed = value.trim();\n const hashIndex = trimmed.lastIndexOf('#');\n const tag = hashIndex === -1 ? trimmed : trimmed.slice(hashIndex + 1);\n if (!FILE_TAG_PATTERN.test(tag)) {\n throw new Error(`Invalid file hash: ${value}. Expected the eight-character tag from @file path#HASH.`);\n }\n return tag;\n}\n\nexport function formatFileHeader(path: string, tag: string): string {\n if (path.length === 0 || path.includes('\\n') || path.includes('\\r')) {\n throw new Error('Invalid file header path. Expected one non-empty path without line breaks.');\n }\n return `@file ${path}#${normalizeFileTag(tag)}`;\n}\n\nexport function parseFileHeader(value: string): FileHeader | undefined {\n const match = FILE_HEADER_PATTERN.exec(value);\n const path = match?.[1];\n const tag = match?.[2];\n return path === undefined || tag === undefined ? undefined : { path, tag };\n}\n\nexport function formatTaggedLine(\n line: string,\n lineNumber: number,\n prefix: TaggedLinePrefix = '',\n anchorSource = line,\n): string {\n assertLineNumber(lineNumber);\n return `${prefix}${lineNumber}#${hashLine(anchorSource)}|${line}`;\n}\n\nexport function formatTaggedLines(lines: readonly string[], firstLine = 1): string {\n return lines.map((line, index) => formatTaggedLine(line, firstLine + index)).join('\\n');\n}\n\nexport function parseTaggedLine(value: string): ParsedTaggedLine | undefined {\n const match = TAGGED_LINE_PATTERN.exec(value);\n const lineText = match?.[2];\n const content = match?.[3];\n if (lineText === undefined || content === undefined) return undefined;\n\n const line = Number.parseInt(lineText, 10);\n if (!Number.isSafeInteger(line) || line < 1) return undefined;\n\n const marker = taggedLineMarker(match?.[1]);\n return marker === undefined ? { content, line } : { content, line, marker };\n}\n\nexport function parseLineAnchor(value: string): LineAnchor {\n if (value.includes('\\n') || value.includes('\\r')) {\n throw new Error(`Invalid line anchor: ${value}. Expected one anchor such as 5#abc, not a pasted multiline block.`);\n }\n\n const match = ANCHOR_PATTERN.exec(value);\n if (!match) {\n throw new Error(\n `Invalid line anchor: ${value}. Expected one anchor such as 5#abc from the latest read or grep result.`,\n );\n }\n\n const line = Number.parseInt(match[1] ?? '', 10);\n const hash = match[2]?.toLowerCase();\n if (!Number.isSafeInteger(line) || line < 1 || hash === undefined) {\n throw new Error(`Invalid line anchor: ${value}. Expected a positive line number and three letters, such as 5#abc.`);\n }\n return { line, hash };\n}\n\nexport function applyHashlineEdits(\n originalContent: string,\n requestedEdits: readonly HashlineRangeInput[],\n): AppliedHashlineEdits {\n const lines = splitLines(originalContent);\n const validated = requestedEdits.map((edit) => validateEdit(edit, lines));\n const edits = deduplicateEdits(validated).sort(compareEdits);\n validateNoOverlaps(edits);\n\n const result = [...lines];\n for (const edit of [...edits].reverse()) {\n const deleteCount = edit.to.line - edit.from.line + 1;\n const replacements = edit.content === null || edit.content === '' ? [] : splitReplacement(edit.content);\n result.splice(edit.from.line - 1, deleteCount, ...replacements);\n }\n return { content: result.join('\\n'), edits };\n}\n\nfunction taggedLineMarker(prefix: string | undefined): TaggedLineMarker | undefined {\n if (prefix === '>> ') return 'match';\n if (prefix === ' ') return 'context';\n return undefined;\n}\n\nfunction assertLineNumber(line: number): void {\n if (!Number.isSafeInteger(line) || line < 1) {\n throw new Error(`Invalid tagged line number: ${String(line)}. Expected a positive safe integer.`);\n }\n}\n\nfunction validateEdit(edit: HashlineRangeInput, lines: readonly string[]): PreparedHashlineEdit {\n const from = parseLineAnchor(edit.from);\n const to = parseLineAnchor(edit.to);\n if (from.line > to.line) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the starting line follows the ending line.`);\n }\n if (to.line > lines.length) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the file has ${lines.length} lines.`);\n }\n\n validateAnchor(from, lines);\n validateAnchor(to, lines);\n return { from, to, content: edit.content ?? null, source: edit };\n}\n\nfunction validateAnchor(anchor: LineAnchor, lines: readonly string[]): void {\n const line = lines[anchor.line - 1];\n if (line === undefined) throw new Error(`Line ${anchor.line} is outside the current file.`);\n const currentHash = hashLine(line);\n if (currentHash !== anchor.hash) {\n throw new Error(\n `Stale line anchor ${anchor.line}#${anchor.hash}. Current anchor is ${anchor.line}#${currentHash}. Re-read the file and retry.`,\n );\n }\n}\n\nfunction deduplicateEdits(edits: readonly PreparedHashlineEdit[]): PreparedHashlineEdit[] {\n const unique = new Map<string, PreparedHashlineEdit>();\n for (const edit of edits) {\n const key = JSON.stringify([edit.from.line, edit.from.hash, edit.to.line, edit.to.hash, edit.content]);\n if (!unique.has(key)) unique.set(key, edit);\n }\n return [...unique.values()];\n}\n\nfunction compareEdits(left: PreparedHashlineEdit, right: PreparedHashlineEdit): number {\n return left.from.line - right.from.line || left.to.line - right.to.line;\n}\n\nfunction validateNoOverlaps(edits: readonly PreparedHashlineEdit[]): void {\n for (let index = 1; index < edits.length; index += 1) {\n const previous = edits[index - 1];\n const current = edits[index];\n if (previous !== undefined && current !== undefined && current.from.line <= previous.to.line) {\n throw new Error(\n `Overlapping edit ranges ${previous.from.line}-${previous.to.line} and ${current.from.line}-${current.to.line}. Merge them and retry.`,\n );\n }\n }\n}\n\nfunction splitReplacement(content: string): string[] {\n const normalized = normalizeToLf(content);\n const trimmed = normalized.endsWith('\\n') ? normalized.slice(0, -1) : normalized;\n return trimmed === '' ? [] : trimmed.split('\\n');\n}\n"],"mappings":"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"}
@@ -1,21 +0,0 @@
1
- import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "../types/hashline.cjs";
2
- //#region src/services/hashline.d.ts
3
- /**
4
- * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.
5
- * Adapted from pulseaiclub/phi writetool under the MIT license.
6
- */
7
- declare function hashLine(line: string): string;
8
- declare function normalizeToLf(text: string): string;
9
- declare function stripBom(text: string): string;
10
- declare function splitLines(text: string): string[];
11
- declare function normalizeFileTag(value: string): string;
12
- declare function formatFileHeader(path: string, tag: string): string;
13
- declare function parseFileHeader(value: string): FileHeader | undefined;
14
- declare function formatTaggedLine(line: string, lineNumber: number, prefix?: TaggedLinePrefix, anchorSource?: string): string;
15
- declare function formatTaggedLines(lines: readonly string[], firstLine?: number): string;
16
- declare function parseTaggedLine(value: string): ParsedTaggedLine | undefined;
17
- declare function parseLineAnchor(value: string): LineAnchor;
18
- declare function applyHashlineEdits(originalContent: string, requestedEdits: readonly HashlineRangeInput[]): AppliedHashlineEdits;
19
- //#endregion
20
- export { applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom };
21
- //# sourceMappingURL=hashline.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hashline.d.cts","names":[],"sources":["../../src/services/hashline.ts"],"mappings":";;;;;;iBAyBgB,SAAS;iBAiBT,cAAc;iBAId,SAAS;iBAIT,WAAW;iBAIX,iBAAiB;iBAUjB,iBAAiB,cAAc;iBAO/B,gBAAgB,gBAAgB;iBAOhC,iBACd,cACA,oBACA,SAAQ,kBACR;iBAMc,kBAAkB,0BAA0B;iBAI5C,gBAAgB,gBAAgB;iBAahC,gBAAgB,gBAAgB;iBAoBhC,mBACd,yBACA,yBAAyB,uBACxB"}
@@ -1,21 +0,0 @@
1
- import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "../types/hashline.mjs";
2
- //#region src/services/hashline.d.ts
3
- /**
4
- * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.
5
- * Adapted from pulseaiclub/phi writetool under the MIT license.
6
- */
7
- declare function hashLine(line: string): string;
8
- declare function normalizeToLf(text: string): string;
9
- declare function stripBom(text: string): string;
10
- declare function splitLines(text: string): string[];
11
- declare function normalizeFileTag(value: string): string;
12
- declare function formatFileHeader(path: string, tag: string): string;
13
- declare function parseFileHeader(value: string): FileHeader | undefined;
14
- declare function formatTaggedLine(line: string, lineNumber: number, prefix?: TaggedLinePrefix, anchorSource?: string): string;
15
- declare function formatTaggedLines(lines: readonly string[], firstLine?: number): string;
16
- declare function parseTaggedLine(value: string): ParsedTaggedLine | undefined;
17
- declare function parseLineAnchor(value: string): LineAnchor;
18
- declare function applyHashlineEdits(originalContent: string, requestedEdits: readonly HashlineRangeInput[]): AppliedHashlineEdits;
19
- //#endregion
20
- export { applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom };
21
- //# sourceMappingURL=hashline.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hashline.d.mts","names":[],"sources":["../../src/services/hashline.ts"],"mappings":";;;;;;iBAyBgB,SAAS;iBAiBT,cAAc;iBAId,SAAS;iBAIT,WAAW;iBAIX,iBAAiB;iBAUjB,iBAAiB,cAAc;iBAO/B,gBAAgB,gBAAgB;iBAOhC,iBACd,cACA,oBACA,SAAQ,kBACR;iBAMc,kBAAkB,0BAA0B;iBAI5C,gBAAgB,gBAAgB;iBAahC,gBAAgB,gBAAgB;iBAoBhC,mBACd,yBACA,yBAAyB,uBACxB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hashline.mjs","names":[],"sources":["../../src/services/hashline.ts"],"sourcesContent":["import type {\n AppliedHashlineEdits,\n FileHeader,\n HashlineRangeInput,\n LineAnchor,\n ParsedTaggedLine,\n PreparedHashlineEdit,\n TaggedLineMarker,\n TaggedLinePrefix,\n} from '../types/hashline.ts';\n\nconst FNV_OFFSET_BASIS = 0xcbf29ce484222325n;\nconst FNV_PRIME = 0x100000001b3n;\nconst ANCHOR_SPACE = 26n * 26n * 26n;\nconst ASCII_A = 97;\nconst FILE_TAG_PATTERN = /^[A-Za-z0-9_-]{8}$/u;\nconst FILE_HEADER_PATTERN = /^@file (.+)#([A-Za-z0-9_-]{8})$/u;\nconst TAGGED_LINE_PATTERN = /^(>> | {3})?(\\d+)#[a-z]{3}\\|(.*)$/u;\nconst ANCHOR_PATTERN = /^\\s*[>+-]*\\s*(\\d+)\\s*[:#]\\s*([a-zA-Z]{3})(?:\\s*\\|.*)?\\s*$/u;\nconst ASCII_WHITESPACE = /[ \\t\\n\\r\\v\\f]/gu;\n\n/**\n * Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.\n * Adapted from pulseaiclub/phi writetool under the MIT license.\n */\nexport function hashLine(line: string): string {\n const bytes = new TextEncoder().encode(line.replace(ASCII_WHITESPACE, ''));\n let hash = FNV_OFFSET_BASIS;\n for (const byte of bytes) {\n hash ^= BigInt(byte);\n hash = BigInt.asUintN(64, hash * FNV_PRIME);\n }\n\n let value = hash % ANCHOR_SPACE;\n const letters = Array.from({ length: 3 }, () => '');\n for (let index = letters.length - 1; index >= 0; index -= 1) {\n letters[index] = String.fromCharCode(ASCII_A + Number(value % 26n));\n value /= 26n;\n }\n return letters.join('');\n}\n\nexport function normalizeToLf(text: string): string {\n return text.replace(/\\r\\n/gu, '\\n').replace(/\\r/gu, '\\n');\n}\n\nexport function stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n}\n\nexport function splitLines(text: string): string[] {\n return normalizeToLf(stripBom(text)).split('\\n');\n}\n\nexport function normalizeFileTag(value: string): string {\n const trimmed = value.trim();\n const hashIndex = trimmed.lastIndexOf('#');\n const tag = hashIndex === -1 ? trimmed : trimmed.slice(hashIndex + 1);\n if (!FILE_TAG_PATTERN.test(tag)) {\n throw new Error(`Invalid file hash: ${value}. Expected the eight-character tag from @file path#HASH.`);\n }\n return tag;\n}\n\nexport function formatFileHeader(path: string, tag: string): string {\n if (path.length === 0 || path.includes('\\n') || path.includes('\\r')) {\n throw new Error('Invalid file header path. Expected one non-empty path without line breaks.');\n }\n return `@file ${path}#${normalizeFileTag(tag)}`;\n}\n\nexport function parseFileHeader(value: string): FileHeader | undefined {\n const match = FILE_HEADER_PATTERN.exec(value);\n const path = match?.[1];\n const tag = match?.[2];\n return path === undefined || tag === undefined ? undefined : { path, tag };\n}\n\nexport function formatTaggedLine(\n line: string,\n lineNumber: number,\n prefix: TaggedLinePrefix = '',\n anchorSource = line,\n): string {\n assertLineNumber(lineNumber);\n return `${prefix}${lineNumber}#${hashLine(anchorSource)}|${line}`;\n}\n\nexport function formatTaggedLines(lines: readonly string[], firstLine = 1): string {\n return lines.map((line, index) => formatTaggedLine(line, firstLine + index)).join('\\n');\n}\n\nexport function parseTaggedLine(value: string): ParsedTaggedLine | undefined {\n const match = TAGGED_LINE_PATTERN.exec(value);\n const lineText = match?.[2];\n const content = match?.[3];\n if (lineText === undefined || content === undefined) return undefined;\n\n const line = Number.parseInt(lineText, 10);\n if (!Number.isSafeInteger(line) || line < 1) return undefined;\n\n const marker = taggedLineMarker(match?.[1]);\n return marker === undefined ? { content, line } : { content, line, marker };\n}\n\nexport function parseLineAnchor(value: string): LineAnchor {\n if (value.includes('\\n') || value.includes('\\r')) {\n throw new Error(`Invalid line anchor: ${value}. Expected one anchor such as 5#abc, not a pasted multiline block.`);\n }\n\n const match = ANCHOR_PATTERN.exec(value);\n if (!match) {\n throw new Error(\n `Invalid line anchor: ${value}. Expected one anchor such as 5#abc from the latest read or grep result.`,\n );\n }\n\n const line = Number.parseInt(match[1] ?? '', 10);\n const hash = match[2]?.toLowerCase();\n if (!Number.isSafeInteger(line) || line < 1 || hash === undefined) {\n throw new Error(`Invalid line anchor: ${value}. Expected a positive line number and three letters, such as 5#abc.`);\n }\n return { line, hash };\n}\n\nexport function applyHashlineEdits(\n originalContent: string,\n requestedEdits: readonly HashlineRangeInput[],\n): AppliedHashlineEdits {\n const lines = splitLines(originalContent);\n const validated = requestedEdits.map((edit) => validateEdit(edit, lines));\n const edits = deduplicateEdits(validated).sort(compareEdits);\n validateNoOverlaps(edits);\n\n const result = [...lines];\n for (const edit of [...edits].reverse()) {\n const deleteCount = edit.to.line - edit.from.line + 1;\n const replacements = edit.content === null || edit.content === '' ? [] : splitReplacement(edit.content);\n result.splice(edit.from.line - 1, deleteCount, ...replacements);\n }\n return { content: result.join('\\n'), edits };\n}\n\nfunction taggedLineMarker(prefix: string | undefined): TaggedLineMarker | undefined {\n if (prefix === '>> ') return 'match';\n if (prefix === ' ') return 'context';\n return undefined;\n}\n\nfunction assertLineNumber(line: number): void {\n if (!Number.isSafeInteger(line) || line < 1) {\n throw new Error(`Invalid tagged line number: ${String(line)}. Expected a positive safe integer.`);\n }\n}\n\nfunction validateEdit(edit: HashlineRangeInput, lines: readonly string[]): PreparedHashlineEdit {\n const from = parseLineAnchor(edit.from);\n const to = parseLineAnchor(edit.to);\n if (from.line > to.line) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the starting line follows the ending line.`);\n }\n if (to.line > lines.length) {\n throw new Error(`Invalid edit range ${edit.from} to ${edit.to}: the file has ${lines.length} lines.`);\n }\n\n validateAnchor(from, lines);\n validateAnchor(to, lines);\n return { from, to, content: edit.content ?? null, source: edit };\n}\n\nfunction validateAnchor(anchor: LineAnchor, lines: readonly string[]): void {\n const line = lines[anchor.line - 1];\n if (line === undefined) throw new Error(`Line ${anchor.line} is outside the current file.`);\n const currentHash = hashLine(line);\n if (currentHash !== anchor.hash) {\n throw new Error(\n `Stale line anchor ${anchor.line}#${anchor.hash}. Current anchor is ${anchor.line}#${currentHash}. Re-read the file and retry.`,\n );\n }\n}\n\nfunction deduplicateEdits(edits: readonly PreparedHashlineEdit[]): PreparedHashlineEdit[] {\n const unique = new Map<string, PreparedHashlineEdit>();\n for (const edit of edits) {\n const key = JSON.stringify([edit.from.line, edit.from.hash, edit.to.line, edit.to.hash, edit.content]);\n if (!unique.has(key)) unique.set(key, edit);\n }\n return [...unique.values()];\n}\n\nfunction compareEdits(left: PreparedHashlineEdit, right: PreparedHashlineEdit): number {\n return left.from.line - right.from.line || left.to.line - right.to.line;\n}\n\nfunction validateNoOverlaps(edits: readonly PreparedHashlineEdit[]): void {\n for (let index = 1; index < edits.length; index += 1) {\n const previous = edits[index - 1];\n const current = edits[index];\n if (previous !== undefined && current !== undefined && current.from.line <= previous.to.line) {\n throw new Error(\n `Overlapping edit ranges ${previous.from.line}-${previous.to.line} and ${current.from.line}-${current.to.line}. Merge them and retry.`,\n );\n }\n }\n}\n\nfunction splitReplacement(content: string): string[] {\n const normalized = normalizeToLf(content);\n const trimmed = normalized.endsWith('\\n') ? normalized.slice(0, -1) : normalized;\n return trimmed === '' ? [] : trimmed.split('\\n');\n}\n"],"mappings":"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"}
@@ -1,34 +0,0 @@
1
- //#region src/types/hashline.d.ts
2
- interface HashlineRangeInput {
3
- readonly from: string;
4
- readonly to: string;
5
- readonly content?: string | null;
6
- }
7
- interface LineAnchor {
8
- readonly line: number;
9
- readonly hash: string;
10
- }
11
- interface PreparedHashlineEdit {
12
- readonly from: LineAnchor;
13
- readonly to: LineAnchor;
14
- readonly content: string | null;
15
- readonly source: HashlineRangeInput;
16
- }
17
- interface AppliedHashlineEdits {
18
- readonly content: string;
19
- readonly edits: readonly PreparedHashlineEdit[];
20
- }
21
- interface FileHeader {
22
- readonly path: string;
23
- readonly tag: string;
24
- }
25
- type TaggedLineMarker = 'match' | 'context';
26
- interface ParsedTaggedLine {
27
- readonly content: string;
28
- readonly line: number;
29
- readonly marker?: TaggedLineMarker;
30
- }
31
- type TaggedLinePrefix = '' | '>> ' | ' ';
32
- //#endregion
33
- export { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, PreparedHashlineEdit, TaggedLineMarker, TaggedLinePrefix };
34
- //# sourceMappingURL=hashline.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hashline.d.cts","names":[],"sources":["../../src/types/hashline.ts"],"mappings":";UAAiB;WACN;WACA;WACA;;UAGM;WACN;WACA;;UAGM;WACN,MAAM;WACN,IAAI;WACJ;WACA,QAAQ;;UAGF;WACN;WACA,gBAAgB;;UAGV;WACN;WACA;;KAGC;UAEK;WACN;WACA;WACA,SAAS;;KAGR"}