dsh-hashline-edit 0.1.1 → 0.2.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.
Files changed (130) hide show
  1. package/README.md +70 -69
  2. package/cordis.patch.yml +1 -1
  3. package/lib/index.js +4143 -4518
  4. package/lib/types/dsh/contracts.d.ts +69 -6
  5. package/lib/types/dsh/fs.d.ts +38 -3
  6. package/lib/types/dsh/guidance.d.ts +5 -5
  7. package/lib/types/dsh/hide.d.ts +78 -0
  8. package/lib/types/dsh/host-compat.d.ts +7 -78
  9. package/lib/types/dsh/index.d.ts +17 -7
  10. package/lib/types/dsh/metrics/events.d.ts +8 -1
  11. package/lib/types/dsh/metrics/instrument.d.ts +20 -14
  12. package/lib/types/dsh/metrics/observer.d.ts +28 -0
  13. package/lib/types/dsh/sandbox.d.ts +2 -2
  14. package/lib/types/dsh/shell-hook.d.ts +17 -8
  15. package/lib/types/dsh/shell-view.d.ts +52 -0
  16. package/lib/types/dsh/tools/apply-patch.d.ts +38 -0
  17. package/lib/types/dsh/tools/context.d.ts +19 -1
  18. package/lib/types/dsh/tools/output.d.ts +32 -4
  19. package/lib/types/dsh/tools/read.d.ts +12 -15
  20. package/lib/types/dsh/tools/undo.d.ts +5 -0
  21. package/lib/types/dsh/write-hook.d.ts +8 -22
  22. package/lib/types/src/apply-patch/commit.d.ts +49 -0
  23. package/lib/types/src/apply-patch/grammar.d.ts +29 -0
  24. package/lib/types/src/apply-patch/lifecycle.d.ts +32 -0
  25. package/lib/types/src/apply-patch/plan.d.ts +50 -0
  26. package/lib/types/src/apply-patch/render.d.ts +51 -0
  27. package/lib/types/src/apply-patch/resolve.d.ts +129 -0
  28. package/lib/types/src/apply-patch/tool.d.ts +54 -0
  29. package/lib/types/src/apply-patch/types.d.ts +362 -0
  30. package/lib/types/src/domain-errors.d.ts +16 -2
  31. package/lib/types/src/edit-undo.d.ts +11 -2
  32. package/lib/types/src/file-content/detection.d.ts +17 -0
  33. package/lib/types/src/file-content/index.d.ts +2 -2
  34. package/lib/types/src/file-content/preview.d.ts +1 -1
  35. package/lib/types/src/fs-lifecycle.d.ts +44 -0
  36. package/lib/types/src/hashline/resolve.d.ts +6 -0
  37. package/lib/types/src/host-utils.d.ts +63 -0
  38. package/lib/types/src/mutation-engine/batch-span-gate.d.ts +6 -0
  39. package/lib/types/src/mutation-engine/edit-request.d.ts +34 -0
  40. package/lib/types/src/mutation-engine/pipeline.d.ts +18 -3
  41. package/lib/types/src/mutation-engine/types.d.ts +50 -58
  42. package/lib/types/src/read-expansion/grammar-manifest.d.ts +4 -2
  43. package/lib/types/src/read-expansion/java-decoration.d.ts +21 -0
  44. package/lib/types/src/read-expansion/span-expansion.d.ts +6 -2
  45. package/lib/types/src/read-expansion/span-kind.d.ts +3 -2
  46. package/lib/types/src/read-expansion/vue-decoration.d.ts +14 -0
  47. package/lib/types/src/served-session/serve-render.d.ts +9 -3
  48. package/lib/types/src/undo-store.d.ts +17 -1
  49. package/package.json +10 -22
  50. package/prompts/apply-patch-guidelines.md +10 -0
  51. package/prompts/apply-patch.md +1 -0
  52. package/prompts/read-guidelines.md +5 -5
  53. package/prompts/read.md +1 -1
  54. package/prompts/undo-last-edit-guidelines.md +2 -2
  55. package/prompts/undo-last-edit-snippet.md +1 -1
  56. package/prompts/undo-last-edit.md +1 -1
  57. package/src/apply-patch/commit.ts +190 -0
  58. package/src/apply-patch/grammar.ts +620 -0
  59. package/src/apply-patch/index.ts +17 -0
  60. package/src/apply-patch/lifecycle.ts +195 -0
  61. package/src/apply-patch/plan.ts +800 -0
  62. package/src/apply-patch/render.ts +137 -0
  63. package/src/apply-patch/resolve.ts +470 -0
  64. package/src/apply-patch/tool.ts +257 -0
  65. package/src/apply-patch/types.ts +387 -0
  66. package/src/cut-repair.ts +77 -11
  67. package/src/domain-errors.ts +58 -10
  68. package/src/edit-undo.ts +379 -25
  69. package/src/file-content/detection.ts +34 -0
  70. package/src/file-content/index.ts +2 -2
  71. package/src/file-content/preview.ts +1 -1
  72. package/src/file-content/served-header.ts +1 -1
  73. package/src/fs-lifecycle.ts +201 -0
  74. package/src/hash-store.ts +18 -1
  75. package/src/hashline/lease-resolve.ts +14 -8
  76. package/src/hashline/resolve.ts +30 -27
  77. package/src/host-utils.ts +224 -0
  78. package/src/mutation-engine/batch-span-gate.ts +17 -4
  79. package/src/mutation-engine/edit-request.ts +39 -0
  80. package/src/mutation-engine/pipeline.ts +535 -63
  81. package/src/mutation-engine/types.ts +49 -65
  82. package/src/mutation-queue.ts +1 -1
  83. package/src/prompts.ts +0 -28
  84. package/src/read-expansion/ast-provider.ts +4 -0
  85. package/src/read-expansion/grammar-manifest.ts +21 -2
  86. package/src/read-expansion/java-decoration.ts +34 -0
  87. package/src/read-expansion/span-expansion.ts +6 -2
  88. package/src/read-expansion/span-kind.ts +3 -1
  89. package/src/read-expansion/vue-decoration.ts +43 -0
  90. package/src/served-session/serve-render.ts +5 -5
  91. package/src/undo-store.ts +74 -5
  92. package/lib/types/dsh/encoding-bridge.d.ts +0 -25
  93. package/lib/types/dsh/encoding.d.ts +0 -144
  94. package/lib/types/dsh/tools/edit.d.ts +0 -10
  95. package/lib/types/src/edit-tool.d.ts +0 -41
  96. package/lib/types/src/mutation-engine/engine.d.ts +0 -31
  97. package/lib/types/src/payload-contract.d.ts +0 -124
  98. package/prompts/edit-guidelines.md +0 -10
  99. package/prompts/edit-snippet.md +0 -1
  100. package/prompts/edit.md +0 -1
  101. package/src/bash-classifier.ts +0 -1626
  102. package/src/bash-conversion/decide.ts +0 -812
  103. package/src/bash-display-transform.ts +0 -50
  104. package/src/bash-multifile-config.ts +0 -239
  105. package/src/bash-view-config.ts +0 -158
  106. package/src/edit-normalize.ts +0 -8
  107. package/src/edit-render.ts +0 -161
  108. package/src/edit-tool.ts +0 -116
  109. package/src/edit.ts +0 -136
  110. package/src/integrations/pi-lens/command.ts +0 -712
  111. package/src/integrations/pi-lens/config.ts +0 -148
  112. package/src/integrations/pi-lens/io-bridge-adapter.ts +0 -234
  113. package/src/integrations/pi-lens/io-bridge.ts +0 -112
  114. package/src/integrations/pi-lens/settings-overlay.ts +0 -272
  115. package/src/lifecycle-hooks/index.ts +0 -1992
  116. package/src/lifecycle-hooks/types.ts +0 -64
  117. package/src/mutated-files.ts +0 -75
  118. package/src/mutation-engine/engine.ts +0 -173
  119. package/src/mutation-engine/index.ts +0 -17
  120. package/src/payload-contract.ts +0 -637
  121. package/src/preview-controller.ts +0 -84
  122. package/src/read.ts +0 -865
  123. package/src/search-hook.ts +0 -41
  124. package/src/store-config.ts +0 -73
  125. package/src/store-janitor-warning.ts +0 -169
  126. package/src/store-janitor.ts +0 -539
  127. package/src/store-kernel/settings.ts +0 -233
  128. package/src/store.ts +0 -154
  129. package/src/tui-presenter.ts +0 -134
  130. package/src/write-hook.ts +0 -130
@@ -0,0 +1,129 @@
1
+ /**
2
+ * apply_patch v4 — resolution layer.
3
+ *
4
+ * Two addressing families meet here:
5
+ *
6
+ * - **fragment mode** is content matching against the pre-call baseline. The
7
+ * `oldLines` sequence (' ' and '-' lines) must match exactly one place in the
8
+ * hunk's window; `@@ <context>` must itself match exactly one line and moves
9
+ * the window start after it. Zero or multiple hits reject with candidate rows.
10
+ * - **identity mode** is served-lease addressing. Both anchors must belong to
11
+ * the `(session, file, anchor)` served set; a missing, retired or foreign
12
+ * anchor rejects with the file's current rows served back.
13
+ *
14
+ * The layer is pure over `FileBaseline` + served anchors: no I/O, no store
15
+ * access. `plan.ts` supplies the baselines and the lease source.
16
+ *
17
+ * @module dsh-hashline-edit/apply-patch/resolve
18
+ */
19
+ import { type LeaseSpanSource } from "../hashline/resolve.js";
20
+ import type { Anchor, FileBaseline, FragmentHunk, IdentityHunk, LineRange, PatchCandidate, PatchPath, PatchProblem, PlannedMutation, PlannedReferenceUse, ReferenceHunk } from "./types.js";
21
+ /** A file's served anchors, parallel to its baseline lines; `null` = never served. */
22
+ export type ServedAnchors = readonly (string | null)[];
23
+ /** The inputs one hunk resolution reads. */
24
+ export interface ResolveContext {
25
+ readonly baseline: FileBaseline;
26
+ readonly served: ServedAnchors;
27
+ /**
28
+ * Read-only lease identity source for identity mode. When present it is
29
+ * authoritative (MVCC lease identity, including rebasing); the `served`
30
+ * mirror is only the fallback for tests/callers with no store lease view.
31
+ */
32
+ readonly identity?: LeaseSpanSource;
33
+ /**
34
+ * Session-wide anchor homes — other files that served this anchor. Used only
35
+ * to diagnose a foreign anchor; a cross-file anchor is refused either way.
36
+ */
37
+ readonly homesOf?: (anchor: Anchor) => readonly PatchPath[];
38
+ }
39
+ /** A successful fragment match in baseline coordinates. */
40
+ export interface FragmentMatch {
41
+ /** Inclusive range covering the hunk's oldLines (' ' + '-'); zero-width when oldLines is empty. */
42
+ readonly range: LineRange;
43
+ /** 1-based line where the search window began (after `@@ <context>`, else 1). */
44
+ readonly windowStart: number;
45
+ }
46
+ /** Fragment match failure: no occurrence or more than one, with candidate rows. */
47
+ export type FragmentMatchResult = {
48
+ readonly ok: true;
49
+ readonly match: FragmentMatch;
50
+ } | {
51
+ readonly ok: false;
52
+ readonly reason: "no-match" | "ambiguous";
53
+ readonly candidates: readonly PatchCandidate[];
54
+ };
55
+ /** Identity resolution outcome. */
56
+ export type IdentityResolveResult = {
57
+ readonly ok: true;
58
+ readonly range: LineRange;
59
+ } | {
60
+ readonly ok: false;
61
+ readonly problems: readonly PatchProblem[];
62
+ };
63
+ /** Reference resolution outcome. */
64
+ export type ReferenceResolveResult = {
65
+ readonly ok: true;
66
+ readonly reference: PlannedReferenceUse;
67
+ /** The destination replacement this reference materializes. */
68
+ readonly mutation: PlannedMutation;
69
+ } | {
70
+ readonly ok: false;
71
+ readonly problems: readonly PatchProblem[];
72
+ };
73
+ /** The baseline's lines; an empty file is zero lines, never one empty line. */
74
+ export declare function baselineLinesOf(baseline: FileBaseline): string[];
75
+ /**
76
+ * Match one fragment hunk against its baseline.
77
+ *
78
+ * Byte-exact: no whitespace, Unicode or newline normalization. The window is
79
+ * `[windowStart, end]` and every subsequent hunk's window begins after the
80
+ * previous hunk's end (the planner enforces the monotonic cursor).
81
+ *
82
+ * @param hunk - the parsed fragment hunk.
83
+ * @param context - baseline plus served anchors (served is unused here; kept for one call shape).
84
+ * @param windowStart - 1-based line the search starts at; defaults to 1.
85
+ * @returns the unique match or the rejection with candidates.
86
+ */
87
+ export declare function matchFragmentHunk(hunk: FragmentHunk, context: ResolveContext, windowStart?: number): FragmentMatchResult;
88
+ /**
89
+ * Resolve one identity hunk (`@@<a>` / `@@<a>-<b>`) against the served set.
90
+ *
91
+ * The anchors must be present in `context.served` at exactly one position each
92
+ * (or hold a live lease when `context.identity` is present), `from` not after
93
+ * `to`. Every failure uses `E_STALE_ANCHOR` with the anchor named, so the
94
+ * planner can serve the file's current rows for a retry.
95
+ *
96
+ * @param hunk - the parsed identity hunk.
97
+ * @param context - baseline plus served anchors / lease source.
98
+ * @returns the resolved inclusive range, or all problems for this hunk.
99
+ */
100
+ export declare function resolveIdentityHunk(hunk: IdentityHunk, context: ResolveContext): IdentityResolveResult;
101
+ /**
102
+ * Resolve one `*** Copy From:` / `*** Cut From:` hunk.
103
+ *
104
+ * The target addressing must be identity mode (`@@<a>` / `@@<a>-<b>`): a
105
+ * fragment-mode reference hunk is refused with `E_PATCH_SYNTAX` at the
106
+ * reference line. The source span must be served for the declared source file;
107
+ * the destination is replaced by the source bytes. A `cut` also plans the
108
+ * source retirement, in the same transaction, as a deletion over the same
109
+ * anchors.
110
+ *
111
+ * @param hunk - the parsed reference hunk.
112
+ * @param destination - the target file's baseline + served anchors / lease source.
113
+ * @param source - the source file's baseline + served anchors / lease source.
114
+ * @returns the materialized reference and destination mutation, or the problems.
115
+ */
116
+ export declare function resolveReferenceHunk(hunk: ReferenceHunk, destination: ResolveContext, source: ResolveContext | FileBaseline, sourceServed?: ServedAnchors): ReferenceResolveResult;
117
+ /**
118
+ * Check one file's resolved mutations for overlap.
119
+ *
120
+ * Two mutations whose target ranges intersect reject the whole call with
121
+ * `E_BATCH_ABORT`-shaped evidence (both patch lines named). Zero-width
122
+ * insertions at the same point are ordered by submission, not treated as
123
+ * overlap; every other intersection is a rejection.
124
+ *
125
+ * @param mutations - one file's mutations, in submission order.
126
+ * @param path - the target file for the problem's path field.
127
+ * @returns every overlap problem found (empty when disjoint).
128
+ */
129
+ export declare function checkMutationOverlaps(mutations: readonly PlannedMutation[], path?: PatchPath): readonly PatchProblem[];
@@ -0,0 +1,54 @@
1
+ /**
2
+ * apply_patch v4 — tool use case.
3
+ *
4
+ * The deep module the dsh host adapter calls. One `execute` owns the whole pipeline for one call:
5
+ *
6
+ * grammar.parseApplyPatch → plan.planApplyPatch → commit.commitPlan → render
7
+ *
8
+ * and returns the model-facing `{ content, details }` envelope. A rejection carries the rendered
9
+ * `[E_*]` envelope and is marked `isError`; nothing was written because plan/grammar own every
10
+ * pre-commit check and commit rolls back or reports its recovery report.
11
+ *
12
+ * The orchestration deps are injectable so the transaction lane can test the complete
13
+ * parse→plan→commit→render path before the grammar/resolve lanes merge into this branch.
14
+ *
15
+ * @module dsh-hashline-edit/apply-patch/tool
16
+ */
17
+ import { parseApplyPatch, type ParsePatchOptions } from "./grammar.js";
18
+ import { planApplyPatch, type PlanApplyPatchOptions } from "./plan.js";
19
+ import { type CommitPlanOptions, type CommitPlanResult } from "./commit.js";
20
+ import { renderFailure, renderSuccess } from "./render.js";
21
+ import type { ApplyPatchContext, ApplyPatchToolResult, PatchPlan, PlanPatchResult } from "./types.js";
22
+ /** The registered tool name; `apply_patch` is the only mutation entry point. */
23
+ export declare const APPLY_PATCH_TOOL_NAME = "apply_patch";
24
+ /** The use-case seam: one narrow interface over the pipeline. */
25
+ export interface ApplyPatchTool {
26
+ /**
27
+ * Execute one apply_patch call.
28
+ * @param input - the raw patch text.
29
+ * @param ctx - live cwd, session identity and cancellation.
30
+ * @returns the model-facing envelope; `isError` marks a rejection (nothing written).
31
+ */
32
+ execute(input: string, ctx: ApplyPatchContext): Promise<ApplyPatchToolResult>;
33
+ /**
34
+ * Parse + plan without writing, for previews and tests.
35
+ * @param input - the raw patch text.
36
+ * @param ctx - live cwd, session identity and cancellation.
37
+ * @returns the plan or every collected problem.
38
+ */
39
+ plan(input: string, ctx: ApplyPatchContext): Promise<PlanPatchResult>;
40
+ }
41
+ /** The injectable seams of the pipeline; production uses the real modules. */
42
+ export interface ApplyPatchToolDeps {
43
+ readonly parse?: (input: string, options?: ParsePatchOptions) => ReturnType<typeof parseApplyPatch>;
44
+ readonly plan?: (document: Parameters<typeof planApplyPatch>[0], options: PlanApplyPatchOptions) => ReturnType<typeof planApplyPatch>;
45
+ readonly commit?: (plan: PatchPlan, options: CommitPlanOptions) => Promise<CommitPlanResult>;
46
+ readonly renderSuccess?: typeof renderSuccess;
47
+ readonly renderFailure?: typeof renderFailure;
48
+ }
49
+ /**
50
+ * Build the apply_patch use case.
51
+ * @param deps - optional seam overrides; production binds the real modules.
52
+ * @returns the tool seam bound to the real hashline engine and store.
53
+ */
54
+ export declare function createApplyPatchTool(deps?: ApplyPatchToolDeps): ApplyPatchTool;
@@ -0,0 +1,362 @@
1
+ /**
2
+ * apply_patch v4 — shared contract types.
3
+ *
4
+ * This module is the frozen vocabulary every apply-patch lane compiles against:
5
+ *
6
+ * - the **patch AST** the grammar layer produces (`PatchDocument`),
7
+ * - the **plan IR** the planner produces and the transaction layer commits
8
+ * (`PatchPlan`, `PlannedFile`, `PlannedMutation`),
9
+ * - the **result / error** types the renderer and the host adapter consume.
10
+ *
11
+ * The engine underneath is the existing hashline kernel: content-addressed 4-char
12
+ * anchors, served leases, MVCC snapshots, correlated multi-file transactions and
13
+ * the undo store. Nothing here re-declares those primitives; the plan IR only
14
+ * adds the addressing and lifecycle vocabulary apply_patch needs.
15
+ *
16
+ * Vocabulary (see GLOSSARY.md): anchor, served row, lease, fragment hunk,
17
+ * identity hunk, reference hunk, patch line, plan.
18
+ *
19
+ * @module dsh-hashline-edit/apply-patch/types
20
+ */
21
+ import type { DomainErrorCode, ServedRow } from "../domain-errors.js";
22
+ import type { LineEnding } from "../edit-diff.js";
23
+ import type { HashStore } from "../hash-store.js";
24
+ import type { TransactionRecoveryReport } from "../mutation-engine/types.js";
25
+ /** Patch AST (grammar layer output). */
26
+ /** A 4-character anchor: `[A-Za-z0-9]{4}` (the hashline alphabet). */
27
+ export type Anchor = string;
28
+ /** A 1-based line number in the submitted patch text, used by every diagnostic. */
29
+ export type PatchLineNumber = number;
30
+ /** A path exactly as written in the patch: workspace-relative or workspace-absolute. */
31
+ export type PatchPath = string;
32
+ /** A closed anchor span; both anchors must have been served for the same file. */
33
+ export interface AnchorSpan {
34
+ readonly from: Anchor;
35
+ readonly to: Anchor;
36
+ }
37
+ /** One parsed `*** Begin Patch` … `*** End Patch` document. */
38
+ export interface PatchDocument {
39
+ /** File operations in submission order. */
40
+ readonly files: readonly PatchFileOp[];
41
+ /** Total number of lines in the input (1-based diagnostics may point at EOF). */
42
+ readonly lineCount: number;
43
+ }
44
+ export type PatchFileOp = AddFileOp | DeleteFileOp | UpdateFileOp;
45
+ /** `*** Add File: <path>` — every following line is a '+' content line. */
46
+ export interface AddFileOp {
47
+ readonly kind: "add";
48
+ readonly path: PatchPath;
49
+ readonly headerLine: PatchLineNumber;
50
+ /** Content lines WITHOUT the leading '+', in file order. Empty means an empty file. */
51
+ readonly contentLines: readonly string[];
52
+ }
53
+ /** `*** Delete File: <path>` */
54
+ export interface DeleteFileOp {
55
+ readonly kind: "delete";
56
+ readonly path: PatchPath;
57
+ readonly headerLine: PatchLineNumber;
58
+ }
59
+ /** `*** Update File: <path>` with its optional `*** Move to:` and zero or more hunks. */
60
+ export interface UpdateFileOp {
61
+ readonly kind: "update";
62
+ readonly path: PatchPath;
63
+ readonly headerLine: PatchLineNumber;
64
+ /** `*** Move to: <path>`; the parser only accepts it directly after the update header. */
65
+ readonly moveTo?: MoveDirective;
66
+ /** Hunks in submission order; each hunk's window starts after the previous hunk's end. */
67
+ readonly hunks: readonly PatchHunk[];
68
+ }
69
+ /** `*** Move to: <path>` */
70
+ export interface MoveDirective {
71
+ readonly path: PatchPath;
72
+ readonly line: PatchLineNumber;
73
+ }
74
+ export type PatchHunk = FragmentHunk | IdentityHunk | ReferenceHunk;
75
+ /**
76
+ * The addressing a hunk opens with.
77
+ *
78
+ * `start` is the bare `@@` (fragment mode, search from the file start);
79
+ * `context` is `@@ <line>` (fragment mode, search window starts after that
80
+ * line's unique hit); `anchors` is `@@<a>` / `@@<a>-<b>` (identity mode).
81
+ */
82
+ export type HunkAddress = {
83
+ readonly kind: "start";
84
+ } | {
85
+ readonly kind: "context";
86
+ readonly text: string;
87
+ } | {
88
+ readonly kind: "anchors";
89
+ readonly span: AnchorSpan;
90
+ };
91
+ /** A context/line matcher hunk: `@@` or `@@ <context>` followed by ' '/'+'/'-' lines. */
92
+ export interface FragmentHunk {
93
+ readonly kind: "fragment";
94
+ /** Grammar guarantees this is `start` or `context`; `anchors` never appears here. */
95
+ readonly address: HunkAddress;
96
+ readonly headerLine: PatchLineNumber;
97
+ /** Body lines, in submission order; at least one is required. */
98
+ readonly lines: readonly FragmentLine[];
99
+ /** `*** End of File` line number when the hunk is tail-pinned, absent otherwise. */
100
+ readonly endOfFile?: PatchLineNumber;
101
+ }
102
+ /** One body line of a fragment hunk. */
103
+ export interface FragmentLine {
104
+ readonly op: "context" | "remove" | "add";
105
+ /** Line content without the leading marker (' ', '-', '+'). */
106
+ readonly text: string;
107
+ readonly line: PatchLineNumber;
108
+ }
109
+ /** An identity hunk: `@@<a>` / `@@<a>-<b>` followed by '+' lines only (empty body = delete). */
110
+ export interface IdentityHunk {
111
+ readonly kind: "identity";
112
+ readonly span: AnchorSpan;
113
+ readonly headerLine: PatchLineNumber;
114
+ /** '+' lines without the marker; empty means the span is deleted. */
115
+ readonly addedLines: readonly string[];
116
+ }
117
+ /** A copy/cut hunk: the content comes from another file's served span; no body lines allowed. */
118
+ export interface ReferenceHunk {
119
+ readonly kind: "reference";
120
+ /**
121
+ * The hunk's own addressing (`@@`, `@@ <context>`, `@@<a>` or `@@<a>-<b>`),
122
+ * which determines the destination position (see docs/apply-patch-v4-internals.md §4).
123
+ */
124
+ readonly address: HunkAddress;
125
+ readonly source: ReferenceSource;
126
+ readonly headerLine: PatchLineNumber;
127
+ }
128
+ /** `*** Copy From:` / `*** Cut From:` source span. */
129
+ export interface ReferenceSource {
130
+ readonly mode: "copy" | "cut";
131
+ readonly path: PatchPath;
132
+ readonly span: AnchorSpan;
133
+ readonly line: PatchLineNumber;
134
+ }
135
+ /** Plan IR (planner output / transaction input). */
136
+ /**
137
+ * An inclusive 1-based line range in a file's pre-call baseline.
138
+ *
139
+ * A zero-width insertion uses the engine's inverted convention:
140
+ * `startLine = point + 1` and `endLine = point` (insert after line `point`;
141
+ * point 0 inserts at the start of the file).
142
+ */
143
+ export interface LineRange {
144
+ readonly startLine: number;
145
+ readonly endLine: number;
146
+ }
147
+ /** The file-lifecycle action planned for one target. */
148
+ export type LifecyclePlan = {
149
+ readonly kind: "keep";
150
+ } | {
151
+ readonly kind: "add";
152
+ } | {
153
+ readonly kind: "delete";
154
+ } | {
155
+ readonly kind: "move";
156
+ readonly to: PatchPath;
157
+ readonly toAbsolutePath: string;
158
+ };
159
+ /** One file's state at call time — the single baseline every mutation resolves against. */
160
+ export interface FileBaseline {
161
+ /** The path as written in the patch. */
162
+ readonly path: PatchPath;
163
+ /** The resolved absolute path the engine reads/writes. */
164
+ readonly absolutePath: string;
165
+ /** LF-normalized text; `""` for an Add File target. */
166
+ readonly text: string;
167
+ /** 4-char anchors parallel to the file's lines; empty for an Add File target. */
168
+ readonly hashes: readonly string[];
169
+ /** Byte-order mark the file carried ("" when none). */
170
+ readonly bom: string;
171
+ /** The file's original line ending. */
172
+ readonly ending: LineEnding;
173
+ /** False for `*** Add File:` targets, true for every other existing target. */
174
+ readonly exists: boolean;
175
+ }
176
+ /** One resolved content mutation against `FileBaseline.text`, in baseline coordinates. */
177
+ export interface PlannedMutation {
178
+ readonly kind: "replace" | "insert" | "delete";
179
+ readonly range: LineRange;
180
+ /** Replacement lines (LF-joined at commit); empty for `delete` and for an empty insertion. */
181
+ readonly lines: readonly string[];
182
+ /** The patch line that produced this mutation (diagnostics and ordering evidence). */
183
+ readonly patchLine: PatchLineNumber;
184
+ }
185
+ /** One Copy/Cut source span materialized from a baseline. */
186
+ export interface PlannedReferenceUse {
187
+ readonly mode: "copy" | "cut";
188
+ readonly path: PatchPath;
189
+ readonly absolutePath: string;
190
+ readonly range: LineRange;
191
+ /** The source bytes, line-split and LF-normalized. */
192
+ readonly lines: readonly string[];
193
+ readonly patchLine: PatchLineNumber;
194
+ }
195
+ /** One file in the plan, with every mutation expressed against its own pre-call baseline. */
196
+ export interface PlannedFile {
197
+ readonly path: PatchPath;
198
+ readonly absolutePath: string;
199
+ readonly lifecycle: LifecyclePlan;
200
+ readonly baseline: FileBaseline;
201
+ /** Non-overlapping mutations in baseline coordinates, ordered by `range.startLine`. */
202
+ readonly mutations: readonly PlannedMutation[];
203
+ /** Copy/Cut sources this target consumes. */
204
+ readonly references: readonly PlannedReferenceUse[];
205
+ /** True when committing this member changes bytes or the namespace. */
206
+ readonly writes: boolean;
207
+ }
208
+ /** The full plan one apply_patch call commits as one correlated transaction. */
209
+ export interface PatchPlan {
210
+ /** Written members in commit order: the primary file leads, the rest follow deterministically. */
211
+ readonly files: readonly PlannedFile[];
212
+ /** Files read only as Copy/Cut sources (never written). */
213
+ readonly referenceFiles: readonly FileBaseline[];
214
+ readonly stats: PatchStats;
215
+ }
216
+ /** Aggregate line/file counts the success summary reports. */
217
+ export interface PatchStats {
218
+ readonly filesChanged: number;
219
+ readonly addedLines: number;
220
+ readonly removedLines: number;
221
+ }
222
+ /** Result and error types. */
223
+ /**
224
+ * The two error codes apply_patch adds to the shared registry.
225
+ *
226
+ * `E_STALE_ANCHOR` is NOT new: `src/domain-errors.ts` already registers it with
227
+ * the served-rows payload this feature reuses. The registry lane must add these
228
+ * two codes (payload + producer) before the surface lane registers the tool.
229
+ */
230
+ export type ApplyPatchNewErrorCode = "E_PATCH_SYNTAX" | "E_UNIQUE_MATCH";
231
+ /** Every code a patch problem may carry. */
232
+ export type ApplyPatchErrorCode = DomainErrorCode | ApplyPatchNewErrorCode;
233
+ /** One candidate row for an ambiguous fragment match, served back to the model. */
234
+ export interface PatchCandidate {
235
+ /** 1-based line number in the baseline. */
236
+ readonly line: number;
237
+ readonly hash: Anchor;
238
+ readonly text: string;
239
+ }
240
+ /** One collectable problem in a rejection; every problem carries its patch line and code. */
241
+ export interface PatchProblem {
242
+ readonly code: ApplyPatchErrorCode;
243
+ readonly patchLine: PatchLineNumber;
244
+ readonly path?: PatchPath;
245
+ readonly message: string;
246
+ /** Candidate rows when the failure is an ambiguous fragment match. */
247
+ readonly candidates?: readonly PatchCandidate[];
248
+ /** 1-based target line used to center the rejection serve window when known. */
249
+ readonly line?: number;
250
+ }
251
+ /** One file's current rows rendered `HASH│content` for reject-and-serve. */
252
+ export interface ServedFileBlock {
253
+ readonly path: PatchPath;
254
+ readonly rows: readonly ServedRow[];
255
+ /** Rendered block, one `HASH│content` row per line. */
256
+ readonly text: string;
257
+ }
258
+ /** One file's lifecycle status and line counts in the success summary. */
259
+ export interface AppliedFileSummary {
260
+ /** A = added, M = modified, R = renamed, D = deleted. */
261
+ readonly status: "A" | "M" | "R" | "D";
262
+ readonly path: PatchPath;
263
+ /** Original path for a rename (`R` only). */
264
+ readonly fromPath?: PatchPath;
265
+ readonly addedLines: number;
266
+ readonly removedLines: number;
267
+ }
268
+ /** Metrics one committed call reports (mirrors the existing RMetrics intent). */
269
+ export interface PatchMetrics {
270
+ readonly files: number;
271
+ readonly hunks: number;
272
+ readonly addedLines: number;
273
+ readonly removedLines: number;
274
+ readonly durationMs?: number;
275
+ }
276
+ /** Successful apply_patch outcome. */
277
+ export interface ApplyPatchSuccess {
278
+ readonly ok: true;
279
+ /** File-level summary, e.g. `Updated 2 files:\nA src/new.ts\nM src/a.ts`. */
280
+ readonly summary: string;
281
+ /** Annotated diff for the UI/details (never the model's primary text). */
282
+ readonly diff: string;
283
+ readonly files: readonly AppliedFileSummary[];
284
+ /** New anchors for every changed line, per file — the second call needs no read. */
285
+ readonly served: readonly ServedFileBlock[];
286
+ readonly warnings: readonly string[];
287
+ readonly metrics: PatchMetrics;
288
+ }
289
+ /** Rejected apply_patch outcome: one transaction, zero disk writes. */
290
+ export interface ApplyPatchFailure {
291
+ readonly ok: false;
292
+ /** Primary code for the envelope; every problem still carries its own code. */
293
+ readonly code: ApplyPatchErrorCode;
294
+ readonly message: string;
295
+ /** Every independently diagnosable problem, in patch order. */
296
+ readonly problems: readonly PatchProblem[];
297
+ /** Current rows for every file named by a problem (reject-and-serve). */
298
+ readonly served: readonly ServedFileBlock[];
299
+ /** A rejection is pre-commit: the disk is unchanged by construction. */
300
+ /**
301
+ * True when the commit left the disk byte-identical (every written member rolled back). A
302
+ * mid-commit failure that could not be rolled back sets this false and reports exactly which
303
+ * members were restored, remain, or are uncertain.
304
+ */
305
+ readonly changedNothing: boolean;
306
+ /** The rollback's three-way outcome, when the failure happened after a member was written. */
307
+ readonly recovery?: TransactionRecoveryReport;
308
+ }
309
+ export type ApplyPatchResult = ApplyPatchSuccess | ApplyPatchFailure;
310
+ /** The grammar seam's total result: a document or every syntax problem found. */
311
+ export type ParsePatchResult = {
312
+ readonly ok: true;
313
+ readonly document: PatchDocument;
314
+ } | {
315
+ readonly ok: false;
316
+ readonly problems: readonly PatchProblem[];
317
+ };
318
+ /** The planner seam's total result. */
319
+ export type PlanPatchResult = {
320
+ readonly ok: true;
321
+ readonly plan: PatchPlan;
322
+ } | {
323
+ readonly ok: false;
324
+ readonly problems: readonly PatchProblem[];
325
+ /** Current rows for every problem file, so the rejection needs no read to retry. */
326
+ readonly served: readonly ServedFileBlock[];
327
+ };
328
+ /** The model-facing tool envelope returned by the renderer / tool use case. */
329
+ export interface ApplyPatchToolResult {
330
+ readonly content: ReadonlyArray<{
331
+ readonly type: "text";
332
+ readonly text: string;
333
+ }>;
334
+ readonly details: ApplyPatchDetails;
335
+ /** True when this envelope renders a rejection (nothing was written). */
336
+ readonly isError?: boolean;
337
+ }
338
+ /** The structured details half of {@link ApplyPatchToolResult}. */
339
+ export interface ApplyPatchDetails {
340
+ readonly summary: string;
341
+ readonly diff: string;
342
+ readonly files: readonly AppliedFileSummary[];
343
+ readonly servedByPath: readonly ServedFileBlock[];
344
+ readonly warnings: readonly string[];
345
+ readonly metrics: PatchMetrics;
346
+ }
347
+ /** Execution facts every apply_patch seam shares. */
348
+ export interface ApplyPatchContext {
349
+ /** Absolute directory relative request paths resolve against (the live session directory). */
350
+ readonly cwd: string;
351
+ /** Session identity that keys served anchors and leases; never empty. */
352
+ readonly sessionKey: string;
353
+ /** Caller cancellation forwarded into every async file operation. */
354
+ readonly signal?: AbortSignal;
355
+ }
356
+ /** Options for the planner/commit seams over the existing hash store. */
357
+ export interface ApplyPatchEngineOptions extends ApplyPatchContext {
358
+ /** Hash store override for tests; the default opens the configured store. */
359
+ readonly store?: HashStore;
360
+ /** POSIX access-mode bitmask; commit defaults to read+write, plan to read-only. */
361
+ readonly accessMode?: number;
362
+ }
@@ -28,13 +28,13 @@
28
28
  * See ADR-0047.
29
29
  */
30
30
  export type Audience = "MODEL" | "USER";
31
- export type DomainErrorCode = "E_BAD_PAYLOAD" | "E_EMPTY_RANGE" | "E_STALE_ANCHOR" | "E_UNKNOWN_ANCHOR" | "E_FOREIGN_ANCHOR" | "E_STALE_RANGE" | "E_TARGET_LOST" | "E_UNVERIFIED_RANGE" | "E_MALFORMED_ANCHOR" | "E_SUSPICIOUS_TEXT" | "E_BATCH_ABORT" | "E_NOOP_LOOP" | "E_UNSUPPORTED_FILE" | "E_ACCESS" | "E_NOT_FOUND" | "E_UNDO_STALE" | "E_UNDO_UNAVAILABLE" | "E_LOSSY_TEXT" | "E_UNDO_REVERT_FAILED" | "E_UNKNOWN" | "E_LARGE_FILE" | "E_INTERNAL_INVARIANT";
31
+ export type DomainErrorCode = "E_BAD_PAYLOAD" | "E_EMPTY_RANGE" | "E_STALE_ANCHOR" | "E_UNKNOWN_ANCHOR" | "E_FOREIGN_ANCHOR" | "E_STALE_RANGE" | "E_TARGET_LOST" | "E_UNVERIFIED_RANGE" | "E_MALFORMED_ANCHOR" | "E_SUSPICIOUS_TEXT" | "E_BATCH_ABORT" | "E_NOOP_LOOP" | "E_UNSUPPORTED_FILE" | "E_ACCESS" | "E_NOT_FOUND" | "E_UNDO_STALE" | "E_UNDO_UNAVAILABLE" | "E_LOSSY_TEXT" | "E_UNDO_REVERT_FAILED" | "E_UNKNOWN" | "E_LARGE_FILE" | "E_INTERNAL_INVARIANT" | "E_PATCH_SYNTAX" | "E_UNIQUE_MATCH";
32
32
  /**
33
33
  * The registered tool whose call produced an `E_BAD_PAYLOAD`. WHY the registered name: the model
34
34
  * reads the tool list, so `undo_last_edit` names the call it must repair and a short form would
35
35
  * name nothing. A tool belongs here only after a live path is measured, never by assumption.
36
36
  */
37
- export type PayloadSubject = "read" | "edit" | "undo_last_edit";
37
+ export type PayloadSubject = "read" | "edit" | "apply_patch" | "undo_last_edit";
38
38
  export declare function isPayloadSubject(value: unknown): value is PayloadSubject;
39
39
  /**
40
40
  * Applied-tier warning codes — the `W_*` namespace (spec
@@ -180,6 +180,20 @@ export interface ErrorPayloadMap {
180
180
  */
181
181
  subject?: PayloadSubject;
182
182
  };
183
+ E_PATCH_SYNTAX: {
184
+ message: string;
185
+ path?: string;
186
+ patchLine?: number;
187
+ servedRows?: ServedRow[];
188
+ servedBlock?: string;
189
+ };
190
+ E_UNIQUE_MATCH: {
191
+ message: string;
192
+ path?: string;
193
+ patchLine?: number;
194
+ servedRows?: ServedRow[];
195
+ servedBlock?: string;
196
+ };
183
197
  }
184
198
  export interface CodeSpec<P> {
185
199
  audience: Audience;
@@ -1,5 +1,5 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
1
  import { type LineEnding } from "./edit-diff.js";
2
+ import type { UndoLifecycle } from "./undo-store.js";
3
3
  export interface UndoEntry {
4
4
  content: string;
5
5
  bom: string;
@@ -26,6 +26,16 @@ export interface UndoEntry {
26
26
  rawPre?: string | null;
27
27
  /** The anchor generation the stored hashes were derived under (0 = pre-generation). */
28
28
  anchorGeneration?: number | null;
29
+ /** The lifecycle action this entry reverses; absent/null is an ordinary edit. */
30
+ lifecycle?: UndoLifecycle | null;
31
+ /** Move: the absolute pre-transaction path. */
32
+ fromPath?: string | null;
33
+ /** Move: the absolute committed path. */
34
+ toPath?: string | null;
35
+ /** The raw post-transaction text when the commit wrote non-canonical bytes (pure move). */
36
+ rawPost?: string | null;
37
+ /** Add: directories the add created, outermost first. */
38
+ createdDirs?: readonly string[] | null;
29
39
  }
30
40
  export declare function saveUndo(path: string, entry: UndoEntry): Promise<{
31
41
  persisted: boolean;
@@ -67,4 +77,3 @@ export declare function executeUndoLastEdit(params: {
67
77
  }, ctx: UndoLastEditContext, options?: {
68
78
  withFileMutationQueue?: UndoMutationQueue;
69
79
  }): Promise<UndoLastEditResult>;
70
- export declare function regEditUndo(pi: ExtensionAPI): void;
@@ -30,6 +30,23 @@ export interface LoadFileOptions {
30
30
  signal?: AbortSignal;
31
31
  }
32
32
  export declare function loadFileKindAndText(filePath: string, options?: LoadFileOptions): Promise<LFile>;
33
+ /**
34
+ * Decode a whole file image as strict UTF-8, stripping a UTF-8 BOM.
35
+ *
36
+ * WHY this helper exists after the multi-encoding governance was removed: the host
37
+ * layer reads files as UTF-8 only, but it still has to name a non-UTF-8 target as
38
+ * such instead of handing the model replacement characters. A fatal decode is the
39
+ * admission test — the bytes are either UTF-8 or they are refused here, and the
40
+ * UTF-8 BOM is the one BOM the text keeps meaning for, stripped so it never
41
+ * reaches a line anchor.
42
+ *
43
+ * The refusal is a model-facing fact plus the next action (the user decides how to
44
+ * convert the file): the message names the path and tells the model to ask.
45
+ * @param path - the model-supplied path, used verbatim in the refusal.
46
+ * @returns the decoded text without a BOM character.
47
+ * @throws DomainError `E_UNSUPPORTED_FILE` when the bytes are not valid UTF-8.
48
+ */
49
+ export declare function decodeUtf8Strict(bytes: Uint8Array, path: string): string;
33
50
  export interface ControlCharReport {
34
51
  /** Total occurrences, not distinct code points. */
35
52
  count: number;
@@ -12,9 +12,9 @@ import { visLines } from "../utils.js";
12
12
  import { type FileStats, type LFile } from "./detection.js";
13
13
  import { type ReadWindow, type ShownRange } from "./preview.js";
14
14
  import type { ServedRow } from "../served-session/serve-render.js";
15
- import type { TruncationResult } from "@earendil-works/pi-coding-agent";
15
+ import type { TruncationResult } from "../host-utils.js";
16
16
  export type { FileStats, LFile, LoadFileOptions } from "./detection.js";
17
- export { loadFileKindAndText } from "./detection.js";
17
+ export { decodeUtf8Strict, loadFileKindAndText } from "./detection.js";
18
18
  export { readNormFile, decodeNormText, fileSnap, snapshotIdFor, statKeyOf, type NormText, type NormFile, type SnapInfo, type StatIdentity, type ReadNormOptions, } from "./loader.js";
19
19
  export { fmtReadPreview, StatIdentityMismatchError } from "./preview.js";
20
20
  export type { ReadWindow } from "./preview.js";
@@ -1,4 +1,4 @@
1
- import { type TruncationResult } from "@earendil-works/pi-coding-agent";
1
+ import { type TruncationResult } from "../host-utils.js";
2
2
  import { type AnchorWalk } from "../hashline/hash-identity.js";
3
3
  import type { ServedRow } from "../served-session/serve-render.js";
4
4
  /** The lines one page actually rendered, 1-based and inclusive. */