@agimon-ai/doompi-hashline 0.0.1-alpha.39 → 0.0.1-alpha.40
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.
- package/dist/files.cjs +1 -1
- package/dist/files.d.cts +1 -1
- package/dist/files.d.mts +1 -1
- package/dist/files.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{adapters/node/files.cjs → services/files/index.cjs} +1 -1
- package/dist/services/files/index.cjs.map +1 -0
- package/dist/{adapters/node/files.d.mts → services/files/index.d.cts} +2 -2
- package/dist/services/files/index.d.cts.map +1 -0
- package/dist/{adapters/node/files.d.cts → services/files/index.d.mts} +2 -2
- package/dist/services/files/index.d.mts.map +1 -0
- package/dist/{adapters/node/files.mjs → services/files/index.mjs} +1 -1
- package/dist/services/files/index.mjs.map +1 -0
- package/dist/services/{hashline.cjs → hashline/index.cjs} +1 -1
- package/dist/services/hashline/index.cjs.map +1 -0
- package/dist/services/{hashline.d.cts → hashline/index.d.cts} +3 -3
- package/dist/services/hashline/index.d.cts.map +1 -0
- package/dist/services/{hashline.d.mts → hashline/index.d.mts} +3 -3
- package/dist/services/hashline/index.d.mts.map +1 -0
- package/dist/services/{hashline.mjs → hashline/index.mjs} +1 -1
- package/dist/services/hashline/index.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/adapters/node/files.cjs.map +0 -1
- package/dist/adapters/node/files.d.cts.map +0 -1
- package/dist/adapters/node/files.d.mts.map +0 -1
- package/dist/adapters/node/files.mjs.map +0 -1
- package/dist/services/hashline.cjs.map +0 -1
- package/dist/services/hashline.d.cts.map +0 -1
- package/dist/services/hashline.d.mts.map +0 -1
- package/dist/services/hashline.mjs.map +0 -1
package/dist/files.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
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.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath } from "./
|
|
1
|
+
import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath } from "./services/files/index.cjs";
|
|
2
2
|
export { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath };
|
package/dist/files.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath, resolveReadInputPath } from "./
|
|
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"./
|
|
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.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
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";
|
|
2
|
+
import { applyHashlineEdits, formatFileHeader, formatTaggedLine, formatTaggedLines, hashLine, normalizeFileTag, normalizeToLf, parseFileHeader, parseLineAnchor, parseTaggedLine, splitLines, stripBom } from "./services/hashline/index.cjs";
|
|
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.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=
|
|
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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/services/files/index.d.ts
|
|
2
2
|
export declare function computeFileTag(bytes: Uint8Array): string;
|
|
3
3
|
export declare function decodeUtf8(bytes: Uint8Array, path: string): string;
|
|
4
4
|
/** Return whether the current process can edit an existing file. */
|
|
@@ -8,4 +8,4 @@ export declare function resolveInputPath(input: string, cwd: string): string;
|
|
|
8
8
|
export declare function resolveReadInputPath(input: string, cwd: string): Promise<string>;
|
|
9
9
|
export declare function displayPath(absolutePath: string, cwd: string): string;
|
|
10
10
|
//#endregion
|
|
11
|
-
//# sourceMappingURL=
|
|
11
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","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,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/services/files/index.d.ts
|
|
2
2
|
export declare function computeFileTag(bytes: Uint8Array): string;
|
|
3
3
|
export declare function decodeUtf8(bytes: Uint8Array, path: string): string;
|
|
4
4
|
/** Return whether the current process can edit an existing file. */
|
|
@@ -8,4 +8,4 @@ export declare function resolveInputPath(input: string, cwd: string): string;
|
|
|
8
8
|
export declare function resolveReadInputPath(input: string, cwd: string): Promise<string>;
|
|
9
9
|
export declare function displayPath(absolutePath: string, cwd: string): string;
|
|
10
10
|
//#endregion
|
|
11
|
-
//# sourceMappingURL=
|
|
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=
|
|
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"}
|
|
@@ -8,4 +8,4 @@ const e=26n*26n*26n,t=/^[A-Za-z0-9_-]{8}$/u,n=/^@file (.+)#([A-Za-z0-9_-]{8})$/u
|
|
|
8
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(`
|
|
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=
|
|
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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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "
|
|
2
|
-
//#region src/services/hashline.d.ts
|
|
1
|
+
import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "../../types/hashline.cjs";
|
|
2
|
+
//#region src/services/hashline/index.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.
|
|
5
5
|
* Adapted from pulseaiclub/phi writetool under the MIT license.
|
|
@@ -17,4 +17,4 @@ export declare function parseTaggedLine(value: string): ParsedTaggedLine | undef
|
|
|
17
17
|
export declare function parseLineAnchor(value: string): LineAnchor;
|
|
18
18
|
export declare function applyHashlineEdits(originalContent: string, requestedEdits: readonly HashlineRangeInput[]): AppliedHashlineEdits;
|
|
19
19
|
//#endregion
|
|
20
|
-
//# sourceMappingURL=
|
|
20
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "
|
|
2
|
-
//#region src/services/hashline.d.ts
|
|
1
|
+
import { AppliedHashlineEdits, FileHeader, HashlineRangeInput, LineAnchor, ParsedTaggedLine, TaggedLinePrefix } from "../../types/hashline.mjs";
|
|
2
|
+
//#region src/services/hashline/index.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Return Phi's three-letter whitespace-insensitive FNV-1a64 line anchor.
|
|
5
5
|
* Adapted from pulseaiclub/phi writetool under the MIT license.
|
|
@@ -17,4 +17,4 @@ export declare function parseTaggedLine(value: string): ParsedTaggedLine | undef
|
|
|
17
17
|
export declare function parseLineAnchor(value: string): LineAnchor;
|
|
18
18
|
export declare function applyHashlineEdits(originalContent: string, requestedEdits: readonly HashlineRangeInput[]): AppliedHashlineEdits;
|
|
19
19
|
//#endregion
|
|
20
|
-
//# sourceMappingURL=
|
|
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"}
|
|
@@ -8,4 +8,4 @@ const e=26n*26n*26n,t=/^[A-Za-z0-9_-]{8}$/u,n=/^@file (.+)#([A-Za-z0-9_-]{8})$/u
|
|
|
8
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(`
|
|
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=
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.cts","names":[],"sources":["../../../src/adapters/node/files.ts"],"mappings":";wBAUgB,eAAe,OAAO;wBAItB,WAAW,OAAO,YAAY;;wBASxB,eAAe,eAAe;wBAOpC,iBAAiB,eAAe;;wBAW1B,qBAAqB,eAAe,cAAc;wBAgBxD,YAAY,sBAAsB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.mts","names":[],"sources":["../../../src/adapters/node/files.ts"],"mappings":";wBAUgB,eAAe,OAAO;wBAItB,WAAW,OAAO,YAAY;;wBASxB,eAAe,eAAe;wBAOpC,iBAAiB,eAAe;;wBAW1B,qBAAqB,eAAe,cAAc;wBAgBxD,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"}
|
|
@@ -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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hashline.d.cts","names":[],"sources":["../../src/services/hashline.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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hashline.d.mts","names":[],"sources":["../../src/services/hashline.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"}
|
|
@@ -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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"}
|