@prometheus-ai/hashline 0.5.0
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/README.md +79 -0
- package/dist/types/apply.d.ts +8 -0
- package/dist/types/block.d.ts +24 -0
- package/dist/types/diff-preview.d.ts +12 -0
- package/dist/types/format.d.ts +76 -0
- package/dist/types/fs.d.ts +80 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/input.d.ts +100 -0
- package/dist/types/messages.d.ts +85 -0
- package/dist/types/mismatch.d.ts +44 -0
- package/dist/types/normalize.d.ts +20 -0
- package/dist/types/parser.d.ts +23 -0
- package/dist/types/patcher.d.ts +109 -0
- package/dist/types/prefixes.d.ts +34 -0
- package/dist/types/recovery.d.ts +40 -0
- package/dist/types/snapshots.d.ts +55 -0
- package/dist/types/stream.d.ts +2 -0
- package/dist/types/tokenizer.d.ts +65 -0
- package/dist/types/types.d.ts +129 -0
- package/package.json +62 -0
- package/src/apply.ts +586 -0
- package/src/block.ts +84 -0
- package/src/diff-preview.ts +49 -0
- package/src/format.ts +134 -0
- package/src/fs.ts +167 -0
- package/src/grammar.lark +25 -0
- package/src/index.ts +17 -0
- package/src/input.ts +423 -0
- package/src/messages.ts +128 -0
- package/src/mismatch.ts +138 -0
- package/src/normalize.ts +38 -0
- package/src/parser.ts +325 -0
- package/src/patcher.ts +392 -0
- package/src/prefixes.ts +132 -0
- package/src/prompt.md +109 -0
- package/src/recovery.ts +186 -0
- package/src/snapshots.ts +128 -0
- package/src/stream.ts +132 -0
- package/src/tokenizer.ts +471 -0
- package/src/types.ts +132 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Filesystem } from "./fs";
|
|
2
|
+
import type { Patch, PatchSection } from "./input";
|
|
3
|
+
import { type LineEnding } from "./normalize";
|
|
4
|
+
import { Recovery } from "./recovery";
|
|
5
|
+
import type { SnapshotStore } from "./snapshots";
|
|
6
|
+
import type { ApplyResult, BlockResolver } from "./types";
|
|
7
|
+
export interface PatcherOptions {
|
|
8
|
+
/** Storage backend used for all reads and writes. */
|
|
9
|
+
fs: Filesystem;
|
|
10
|
+
/** Snapshot store that minted and resolves hashline section tags. Required. */
|
|
11
|
+
snapshots: SnapshotStore;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves `replace block N:` anchors to concrete line spans via tree-sitter.
|
|
14
|
+
* Optional: when omitted, any `replace block N:` edit throws on apply (the
|
|
15
|
+
* host did not wire a resolver). Plain line-range ops never need it.
|
|
16
|
+
*/
|
|
17
|
+
blockResolver?: BlockResolver;
|
|
18
|
+
}
|
|
19
|
+
/** Per-section result returned by {@link Patcher.apply} / {@link Patcher.commit}. */
|
|
20
|
+
export interface PatchSectionResult {
|
|
21
|
+
/** Section path (as authored, after cwd-resolution at parse time). */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Filesystem-canonical key for this section (e.g. absolute path). */
|
|
24
|
+
canonicalPath: string;
|
|
25
|
+
/** `"noop"` when the apply produced no change; otherwise `"create"` / `"update"`. */
|
|
26
|
+
op: "create" | "update" | "noop";
|
|
27
|
+
/** Pre-edit text (LF-normalized, BOM-stripped). */
|
|
28
|
+
before: string;
|
|
29
|
+
/** Post-edit text (LF-normalized, BOM-stripped). For `"noop"` equals `before`. */
|
|
30
|
+
after: string;
|
|
31
|
+
/** Same text as `after` but with the original BOM and line ending restored. */
|
|
32
|
+
persisted: string;
|
|
33
|
+
/** Final text that the {@link Filesystem} actually wrote (may differ if the FS transformed it). */
|
|
34
|
+
written: string;
|
|
35
|
+
/** 4-hex content-hash tag for `after`. Use to anchor follow-up edits. */
|
|
36
|
+
fileHash: string;
|
|
37
|
+
/** Hashline section header (`[path#tag]`) of the post-edit content. */
|
|
38
|
+
header: string;
|
|
39
|
+
/** 1-indexed first changed line in `after`, or `undefined` for noops. */
|
|
40
|
+
firstChangedLine?: number;
|
|
41
|
+
/** Warnings collected by the parser, applier, and (optionally) recovery. */
|
|
42
|
+
warnings: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface PatcherApplyResult {
|
|
45
|
+
sections: PatchSectionResult[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Opaque token returned by {@link Patcher.prepare}. Carries the section, the
|
|
49
|
+
* raw file content read off disk, and the in-memory apply result.
|
|
50
|
+
* {@link Patcher.commit} just writes the {@link PreparedSection.applyResult}.
|
|
51
|
+
*/
|
|
52
|
+
export declare class PreparedSection {
|
|
53
|
+
readonly section: PatchSection;
|
|
54
|
+
readonly canonicalPath: string;
|
|
55
|
+
readonly exists: boolean;
|
|
56
|
+
readonly rawContent: string;
|
|
57
|
+
readonly bom: string;
|
|
58
|
+
readonly lineEnding: LineEnding;
|
|
59
|
+
readonly normalized: string;
|
|
60
|
+
readonly applyResult: ApplyResult;
|
|
61
|
+
readonly parseWarnings: readonly string[];
|
|
62
|
+
/** @internal */
|
|
63
|
+
constructor(section: PatchSection, canonicalPath: string, exists: boolean, rawContent: string, bom: string, lineEnding: LineEnding, normalized: string, applyResult: ApplyResult, parseWarnings: readonly string[]);
|
|
64
|
+
/** Convenience: returns true when the apply produced no change. */
|
|
65
|
+
get isNoop(): boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* High-level patcher. Wires a {@link Filesystem} and a required
|
|
69
|
+
* {@link SnapshotStore} together with the parsing + applying core.
|
|
70
|
+
*
|
|
71
|
+
* Construct once per FS configuration; reuse across patches.
|
|
72
|
+
*/
|
|
73
|
+
export declare class Patcher {
|
|
74
|
+
#private;
|
|
75
|
+
readonly fs: Filesystem;
|
|
76
|
+
readonly snapshots: SnapshotStore;
|
|
77
|
+
readonly recovery: Recovery;
|
|
78
|
+
readonly blockResolver: BlockResolver | undefined;
|
|
79
|
+
constructor(options: PatcherOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Apply every section in `patch`. `prepare` runs the full apply for each
|
|
82
|
+
* section in memory before any write hits the filesystem, so a
|
|
83
|
+
* multi-section batch is naturally all-or-nothing. Returns one
|
|
84
|
+
* {@link PatchSectionResult} per section in the original patch order.
|
|
85
|
+
*/
|
|
86
|
+
apply(patch: Patch): Promise<PatcherApplyResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Run the preflight pass only: read, parse, validate, apply-in-memory.
|
|
89
|
+
* No writes hit the filesystem. Use for CI checks and dry runs.
|
|
90
|
+
*/
|
|
91
|
+
preflight(patch: Patch): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Read a section's target file, parse the section, validate the snapshot
|
|
94
|
+
* tag (with recovery), and apply the edits in memory. Returns a
|
|
95
|
+
* {@link PreparedSection} which can be fed to {@link commit} to land
|
|
96
|
+
* the result on the filesystem.
|
|
97
|
+
*
|
|
98
|
+
* Throws on parse error, missing-file-for-anchored-edit, or unrecovered
|
|
99
|
+
* tag mismatch ({@link MismatchError}).
|
|
100
|
+
*/
|
|
101
|
+
prepare(section: PatchSection): Promise<PreparedSection>;
|
|
102
|
+
/**
|
|
103
|
+
* Commit a previously {@link prepare}d section to the filesystem.
|
|
104
|
+
* Restores line endings and BOM, writes via the {@link Filesystem}, and
|
|
105
|
+
* records a fresh snapshot in the {@link SnapshotStore} keyed by the
|
|
106
|
+
* filesystem-canonical path.
|
|
107
|
+
*/
|
|
108
|
+
commit(prepared: PreparedSection): Promise<PatchSectionResult>;
|
|
109
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When a hashline payload is authored against `read`/`search` output, each
|
|
3
|
+
* line is prefixed with either a hashline-mode line number (`123:`) or, for
|
|
4
|
+
* diff-style echoes, a leading `+`. These helpers detect that and recover
|
|
5
|
+
* the raw text. Two strip modes are exposed:
|
|
6
|
+
*
|
|
7
|
+
* - {@link stripNewLinePrefixes} — opportunistic: strips when the input
|
|
8
|
+
* clearly carries hashline or diff prefixes, leaves it alone otherwise.
|
|
9
|
+
* - {@link stripHashlinePrefixes} — strict: only strips when every non-empty
|
|
10
|
+
* content line is hashline-prefixed.
|
|
11
|
+
*
|
|
12
|
+
* These run *before* the tokenizer; they exist because hashline mode is the
|
|
13
|
+
* common case for echoed file content, and erroneously echoed prefixes will
|
|
14
|
+
* otherwise turn every content line into a (malformed) op.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Strip whichever prefix scheme the lines appear to be carrying:
|
|
18
|
+
* - hashline line-number prefixes (`123:`) when every content line has one
|
|
19
|
+
* - leading `+` (diff style) when at least half the lines have one
|
|
20
|
+
* - mixed `+<n>:` form when present
|
|
21
|
+
*
|
|
22
|
+
* Returns the lines untouched if no scheme is recognized.
|
|
23
|
+
*/
|
|
24
|
+
export declare function stripNewLinePrefixes(lines: string[]): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Strict variant: strip hashline prefixes only when every content line is
|
|
27
|
+
* hashline-prefixed. Returns the lines unchanged otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stripHashlinePrefixes(lines: string[]): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Normalize line payloads by stripping read/search line prefixes. `null` /
|
|
32
|
+
* `undefined` yield `[]`; a single multiline string is split on `\n`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function hashlineParseText(edit: string[] | string | null | undefined): string[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SnapshotStore } from "./snapshots";
|
|
2
|
+
import type { Edit } from "./types";
|
|
3
|
+
export interface RecoveryArgs {
|
|
4
|
+
path: string;
|
|
5
|
+
currentText: string;
|
|
6
|
+
fileHash: string;
|
|
7
|
+
edits: readonly Edit[];
|
|
8
|
+
}
|
|
9
|
+
export interface RecoveryResult {
|
|
10
|
+
/** Post-recovery text. */
|
|
11
|
+
text: string;
|
|
12
|
+
/** First changed line (1-indexed) relative to the live `currentText`, or `undefined`. */
|
|
13
|
+
firstChangedLine: number | undefined;
|
|
14
|
+
/** Warnings collected during recovery, including the user-facing recovery banner. */
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Stateless recovery driver over a {@link SnapshotStore}. Construct once and
|
|
19
|
+
* call {@link Recovery.tryRecover} per stale-tag incident. The default
|
|
20
|
+
* implementation tries two strategies in order:
|
|
21
|
+
*
|
|
22
|
+
* 1. Apply the edits on the full-file version the tag names, then 3-way-merge
|
|
23
|
+
* the resulting patch onto the live content (handles external writes).
|
|
24
|
+
* 2. (Session chain) If that version wasn't the head, replay the edits onto
|
|
25
|
+
* the live content directly when line counts match AND every edit's anchor
|
|
26
|
+
* line content is unchanged between version and current — a prior in-session
|
|
27
|
+
* edit advanced the tag and the model's anchors still name the same logical
|
|
28
|
+
* rows. Emits a dedicated {@link RECOVERY_SESSION_REPLAY_WARNING} because
|
|
29
|
+
* even with both guards a coincidental insert+delete pair on duplicate rows
|
|
30
|
+
* can still land the edit on the wrong row; see {@link replaySessionChainOnCurrent}.
|
|
31
|
+
*/
|
|
32
|
+
export declare class Recovery {
|
|
33
|
+
readonly store: SnapshotStore;
|
|
34
|
+
constructor(store: SnapshotStore);
|
|
35
|
+
/**
|
|
36
|
+
* Attempt recovery. Returns `null` when no path forward is found — the
|
|
37
|
+
* caller should then surface a {@link MismatchError}.
|
|
38
|
+
*/
|
|
39
|
+
tryRecover(args: RecoveryArgs): RecoveryResult | null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One full-file version observed at a point in time. The tag the model sees is
|
|
3
|
+
* {@link Snapshot.hash}; recovery replays edits against {@link Snapshot.text}.
|
|
4
|
+
*/
|
|
5
|
+
export interface Snapshot {
|
|
6
|
+
/** Canonical path this version belongs to. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
/** Full normalized (LF, no BOM) file text as observed. */
|
|
9
|
+
readonly text: string;
|
|
10
|
+
/** Content-derived tag for {@link Snapshot.text} (see {@link computeFileHash}). */
|
|
11
|
+
readonly hash: string;
|
|
12
|
+
/** Timestamp (ms since epoch) the version was recorded. */
|
|
13
|
+
recordedAt: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Storage seam for full-file version snapshots. The patcher calls {@link head}
|
|
17
|
+
* for the latest version of a path and {@link byHash} when it needs the
|
|
18
|
+
* specific historical version a section's stale tag names.
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class SnapshotStore {
|
|
21
|
+
/** Most-recently recorded version for `path`, or `null` if none. */
|
|
22
|
+
abstract head(path: string): Snapshot | null;
|
|
23
|
+
/** Recorded version for `path` whose tag equals `hash`, or `null`. */
|
|
24
|
+
abstract byHash(path: string, hash: string): Snapshot | null;
|
|
25
|
+
/** Record the full normalized text of `path` and return its content tag. */
|
|
26
|
+
abstract record(path: string, fullText: string): string;
|
|
27
|
+
/** Drop the version history for a single path. */
|
|
28
|
+
abstract invalidate(path: string): void;
|
|
29
|
+
/** Drop every version history. */
|
|
30
|
+
abstract clear(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface InMemorySnapshotStoreOptions {
|
|
33
|
+
/** Maximum number of distinct paths tracked at once (default 30). LRU eviction. */
|
|
34
|
+
maxPaths?: number;
|
|
35
|
+
/** Maximum full-file versions retained per path (default 4). Oldest dropped first. */
|
|
36
|
+
maxVersionsPerPath?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* In-memory {@link SnapshotStore} backed by `lru-cache`. Per-path history is a
|
|
40
|
+
* short ring of full-file versions (oldest dropped first); per-session path
|
|
41
|
+
* tracking is LRU-bounded so cold paths age out automatically.
|
|
42
|
+
*
|
|
43
|
+
* Recording byte-identical content again refreshes recency and reuses the
|
|
44
|
+
* existing tag (read fusion); recording new content unshifts a fresh version
|
|
45
|
+
* onto the front of the path history.
|
|
46
|
+
*/
|
|
47
|
+
export declare class InMemorySnapshotStore extends SnapshotStore {
|
|
48
|
+
#private;
|
|
49
|
+
constructor(options?: InMemorySnapshotStoreOptions);
|
|
50
|
+
head(path: string): Snapshot | null;
|
|
51
|
+
byHash(path: string, hash: string): Snapshot | null;
|
|
52
|
+
record(path: string, fullText: string): string;
|
|
53
|
+
invalidate(path: string): void;
|
|
54
|
+
clear(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Anchor, Cursor, ParsedRange } from "./types";
|
|
2
|
+
export declare function splitHashlineLines(text: string): string[];
|
|
3
|
+
export declare function cloneCursor(cursor: Cursor): Cursor;
|
|
4
|
+
/** Parse a bare line-number anchor. Throws on malformed input. */
|
|
5
|
+
export declare function parseLid(raw: string, lineNum: number): Anchor;
|
|
6
|
+
export type BlockTarget = {
|
|
7
|
+
kind: "replace";
|
|
8
|
+
range: ParsedRange;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "block";
|
|
11
|
+
anchor: Anchor;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "delete";
|
|
14
|
+
range: ParsedRange;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "delete_block";
|
|
17
|
+
anchor: Anchor;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "insert_before";
|
|
20
|
+
anchor: Anchor;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "insert_after";
|
|
23
|
+
anchor: Anchor;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "bof";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "eof";
|
|
28
|
+
};
|
|
29
|
+
interface TokenBase {
|
|
30
|
+
lineNum: number;
|
|
31
|
+
}
|
|
32
|
+
export type Token = (TokenBase & {
|
|
33
|
+
kind: "blank";
|
|
34
|
+
}) | (TokenBase & {
|
|
35
|
+
kind: "envelope-begin";
|
|
36
|
+
}) | (TokenBase & {
|
|
37
|
+
kind: "envelope-end";
|
|
38
|
+
}) | (TokenBase & {
|
|
39
|
+
kind: "abort";
|
|
40
|
+
}) | (TokenBase & {
|
|
41
|
+
kind: "header";
|
|
42
|
+
path: string;
|
|
43
|
+
fileHash?: string;
|
|
44
|
+
}) | (TokenBase & {
|
|
45
|
+
kind: "op-block";
|
|
46
|
+
target: BlockTarget;
|
|
47
|
+
}) | (TokenBase & {
|
|
48
|
+
kind: "payload-literal";
|
|
49
|
+
text: string;
|
|
50
|
+
}) | (TokenBase & {
|
|
51
|
+
kind: "raw";
|
|
52
|
+
text: string;
|
|
53
|
+
});
|
|
54
|
+
export declare class Tokenizer {
|
|
55
|
+
#private;
|
|
56
|
+
feed(chunk: string): Token[];
|
|
57
|
+
end(): Token[];
|
|
58
|
+
reset(): void;
|
|
59
|
+
tokenizeAll(text: string): Token[];
|
|
60
|
+
tokenize(line: string, lineNum?: number): Token;
|
|
61
|
+
isOp(line: string): boolean;
|
|
62
|
+
isHeader(line: string): boolean;
|
|
63
|
+
isEnvelopeMarker(line: string): boolean;
|
|
64
|
+
}
|
|
65
|
+
export type { ParsedRange } from "./types";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure data types shared across the hashline parser, applier, and patcher.
|
|
3
|
+
* Nothing in this file references a filesystem, agent runtime, or schema
|
|
4
|
+
* library — keep it that way.
|
|
5
|
+
*/
|
|
6
|
+
/** A line-number anchor (1-indexed). */
|
|
7
|
+
export interface Anchor {
|
|
8
|
+
line: number;
|
|
9
|
+
}
|
|
10
|
+
/** Where an `insert` edit should land relative to existing content. */
|
|
11
|
+
export type Cursor = {
|
|
12
|
+
kind: "bof";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "eof";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "before_anchor";
|
|
17
|
+
anchor: Anchor;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "after_anchor";
|
|
20
|
+
anchor: Anchor;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A single low-level edit produced by the parser and consumed by the applier.
|
|
24
|
+
* Multi-line replacements decompose to one `insert` per replacement line plus
|
|
25
|
+
* one `delete` per consumed line. Replacement payloads are tagged so the
|
|
26
|
+
* applier can distinguish literal insertion from new content for a deleted
|
|
27
|
+
* line.
|
|
28
|
+
*/
|
|
29
|
+
export type Edit = {
|
|
30
|
+
kind: "insert";
|
|
31
|
+
cursor: Cursor;
|
|
32
|
+
text: string;
|
|
33
|
+
lineNum: number;
|
|
34
|
+
index: number;
|
|
35
|
+
mode?: "replacement";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "delete";
|
|
38
|
+
anchor: Anchor;
|
|
39
|
+
lineNum: number;
|
|
40
|
+
index: number;
|
|
41
|
+
oldAssertion?: string;
|
|
42
|
+
} | {
|
|
43
|
+
/**
|
|
44
|
+
* Deferred block edit (`replace block N:` / `delete block N`). The exact
|
|
45
|
+
* line span is unknown at parse time — it is computed by
|
|
46
|
+
* {@link resolveBlockEdits} once file text + path (→ language) are
|
|
47
|
+
* available, then expanded into concrete edits: a non-empty `payloads`
|
|
48
|
+
* (from `replace block`) becomes the same `replacement` inserts + deletes
|
|
49
|
+
* that `replace start..end:` produces; an empty `payloads` (from `delete
|
|
50
|
+
* block`) becomes a pure range deletion. `applyEdits` never sees this
|
|
51
|
+
* variant.
|
|
52
|
+
*/
|
|
53
|
+
kind: "block";
|
|
54
|
+
anchor: Anchor;
|
|
55
|
+
payloads: string[];
|
|
56
|
+
lineNum: number;
|
|
57
|
+
index: number;
|
|
58
|
+
};
|
|
59
|
+
/** Result of applying a parsed set of edits to a text body. */
|
|
60
|
+
export interface ApplyResult {
|
|
61
|
+
/** Post-edit text body. */
|
|
62
|
+
text: string;
|
|
63
|
+
/** First line number (1-indexed) that changed, or `undefined` for a no-op apply. */
|
|
64
|
+
firstChangedLine?: number;
|
|
65
|
+
/** Diagnostic warnings collected by the parser, patcher, or recovery. */
|
|
66
|
+
warnings?: string[];
|
|
67
|
+
}
|
|
68
|
+
/** A parsed `[A..B]` line range. */
|
|
69
|
+
export interface ParsedRange {
|
|
70
|
+
start: Anchor;
|
|
71
|
+
end: Anchor;
|
|
72
|
+
}
|
|
73
|
+
/** Optional hints for {@link splitPatchInput}. */
|
|
74
|
+
export interface SplitOptions {
|
|
75
|
+
/** Resolves absolute paths inside hashline headers to cwd-relative form. */
|
|
76
|
+
cwd?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Fallback path used when the input lacks a `[PATH]` header but contains
|
|
79
|
+
* recognizable hashline operations. Lets streaming previews work before
|
|
80
|
+
* the model has written the header.
|
|
81
|
+
*/
|
|
82
|
+
path?: string;
|
|
83
|
+
}
|
|
84
|
+
/** Streaming-formatter knobs for {@link streamHashLines}. */
|
|
85
|
+
export interface StreamOptions {
|
|
86
|
+
/** First line number to use when formatting (1-indexed, default 1). */
|
|
87
|
+
startLine?: number;
|
|
88
|
+
/** Maximum formatted lines per yielded chunk (default 200). */
|
|
89
|
+
maxChunkLines?: number;
|
|
90
|
+
/** Maximum UTF-8 bytes per yielded chunk (default 64 KiB). */
|
|
91
|
+
maxChunkBytes?: number;
|
|
92
|
+
}
|
|
93
|
+
/** Result of {@link buildCompactDiffPreview}. */
|
|
94
|
+
export interface CompactDiffPreview {
|
|
95
|
+
preview: string;
|
|
96
|
+
addedLines: number;
|
|
97
|
+
removedLines: number;
|
|
98
|
+
}
|
|
99
|
+
/** Optional knobs for {@link buildCompactDiffPreview}. Reserved for future use. */
|
|
100
|
+
export interface CompactDiffOptions {
|
|
101
|
+
/** Maximum entries kept on each side of an unchanged-context truncation (default 2). */
|
|
102
|
+
maxUnchangedRun?: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolved 1-indexed inclusive line span of a `replace block N:` target.
|
|
106
|
+
*/
|
|
107
|
+
export interface BlockSpan {
|
|
108
|
+
/** First line of the block (1-indexed, inclusive). */
|
|
109
|
+
start: number;
|
|
110
|
+
/** Last line of the block (1-indexed, inclusive). */
|
|
111
|
+
end: number;
|
|
112
|
+
}
|
|
113
|
+
/** Request handed to a {@link BlockResolver} to resolve one `replace block N:` anchor. */
|
|
114
|
+
export interface BlockResolverRequest {
|
|
115
|
+
/** Target file path (used to infer language by extension). */
|
|
116
|
+
path: string;
|
|
117
|
+
/** Full text the block must be resolved against (the snapshot the tag names). */
|
|
118
|
+
text: string;
|
|
119
|
+
/** 1-indexed line the block must begin on. */
|
|
120
|
+
line: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolves a `replace block N:` anchor to the line span of the syntactic block
|
|
124
|
+
* that begins on line N. Returns `null` when no block can be resolved
|
|
125
|
+
* (unrecognized language, blank/out-of-range line, no node begins there, or the
|
|
126
|
+
* resolved subtree has a syntax error). Pure seam: the hashline core declares
|
|
127
|
+
* the contract; the host injects a tree-sitter-backed implementation.
|
|
128
|
+
*/
|
|
129
|
+
export type BlockResolver = (request: BlockResolverRequest) => BlockSpan | null;
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@prometheus-ai/hashline",
|
|
4
|
+
"version": "0.5.0",
|
|
5
|
+
"description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.",
|
|
6
|
+
"homepage": "https://prometheus.trivlab.com",
|
|
7
|
+
"author": "Uttam Trivedi",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/uttamtrivedi/Prometheus.git",
|
|
12
|
+
"directory": "packages/hashline"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/uttamtrivedi/Prometheus/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"patch",
|
|
19
|
+
"diff",
|
|
20
|
+
"edit",
|
|
21
|
+
"hashline",
|
|
22
|
+
"agent",
|
|
23
|
+
"llm"
|
|
24
|
+
],
|
|
25
|
+
"main": "./src/index.ts",
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"check": "biome check . && bun run check:types",
|
|
29
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
30
|
+
"lint": "biome lint .",
|
|
31
|
+
"test": "bun test --parallel",
|
|
32
|
+
"fix": "biome check --write --unsafe .",
|
|
33
|
+
"fmt": "biome format --write ."
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"diff": "^9.0.0",
|
|
37
|
+
"lru-cache": "11.5.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/bun": "^1.3.14"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"bun": ">=1.3.14"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"src",
|
|
47
|
+
"dist/types"
|
|
48
|
+
],
|
|
49
|
+
"exports": {
|
|
50
|
+
".": {
|
|
51
|
+
"types": "./dist/types/index.d.ts",
|
|
52
|
+
"import": "./src/index.ts"
|
|
53
|
+
},
|
|
54
|
+
"./grammar.lark": "./src/grammar.lark",
|
|
55
|
+
"./prompt.md": "./src/prompt.md",
|
|
56
|
+
"./*": {
|
|
57
|
+
"types": "./dist/types/*.d.ts",
|
|
58
|
+
"import": "./src/*.ts"
|
|
59
|
+
},
|
|
60
|
+
"./*.js": "./src/*.ts"
|
|
61
|
+
}
|
|
62
|
+
}
|