dsh-hashline-edit 0.1.1 → 0.2.1
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 +70 -69
- package/cordis.patch.yml +3 -3
- package/lib/index.js +4151 -4516
- package/lib/types/dsh/contracts.d.ts +69 -6
- package/lib/types/dsh/fs.d.ts +38 -3
- package/lib/types/dsh/guidance.d.ts +5 -5
- package/lib/types/dsh/hide.d.ts +79 -0
- package/lib/types/dsh/host-compat.d.ts +7 -78
- package/lib/types/dsh/index.d.ts +21 -9
- package/lib/types/dsh/metrics/events.d.ts +8 -1
- package/lib/types/dsh/metrics/instrument.d.ts +20 -14
- package/lib/types/dsh/metrics/observer.d.ts +28 -0
- package/lib/types/dsh/sandbox.d.ts +2 -2
- package/lib/types/dsh/shell-hook.d.ts +17 -8
- package/lib/types/dsh/shell-view.d.ts +52 -0
- package/lib/types/dsh/tools/apply-patch.d.ts +38 -0
- package/lib/types/dsh/tools/context.d.ts +19 -1
- package/lib/types/dsh/tools/output.d.ts +32 -4
- package/lib/types/dsh/tools/read.d.ts +12 -15
- package/lib/types/dsh/tools/undo.d.ts +5 -0
- package/lib/types/dsh/write-hook.d.ts +8 -22
- package/lib/types/src/apply-patch/commit.d.ts +49 -0
- package/lib/types/src/apply-patch/grammar.d.ts +29 -0
- package/lib/types/src/apply-patch/lifecycle.d.ts +32 -0
- package/lib/types/src/apply-patch/plan.d.ts +50 -0
- package/lib/types/src/apply-patch/render.d.ts +51 -0
- package/lib/types/src/apply-patch/resolve.d.ts +129 -0
- package/lib/types/src/apply-patch/tool.d.ts +54 -0
- package/lib/types/src/apply-patch/types.d.ts +362 -0
- package/lib/types/src/domain-errors.d.ts +16 -2
- package/lib/types/src/edit-undo.d.ts +11 -2
- package/lib/types/src/file-content/detection.d.ts +17 -0
- package/lib/types/src/file-content/index.d.ts +2 -2
- package/lib/types/src/file-content/preview.d.ts +1 -1
- package/lib/types/src/fs-lifecycle.d.ts +44 -0
- package/lib/types/src/hashline/resolve.d.ts +6 -0
- package/lib/types/src/host-utils.d.ts +63 -0
- package/lib/types/src/mutation-engine/batch-span-gate.d.ts +6 -0
- package/lib/types/src/mutation-engine/edit-request.d.ts +34 -0
- package/lib/types/src/mutation-engine/pipeline.d.ts +18 -3
- package/lib/types/src/mutation-engine/types.d.ts +50 -58
- package/lib/types/src/read-expansion/grammar-manifest.d.ts +4 -2
- package/lib/types/src/read-expansion/java-decoration.d.ts +21 -0
- package/lib/types/src/read-expansion/span-expansion.d.ts +6 -2
- package/lib/types/src/read-expansion/span-kind.d.ts +3 -2
- package/lib/types/src/read-expansion/vue-decoration.d.ts +14 -0
- package/lib/types/src/served-session/serve-render.d.ts +9 -3
- package/lib/types/src/undo-store.d.ts +17 -1
- package/package.json +18 -22
- package/prompts/apply-patch-guidelines.md +10 -0
- package/prompts/apply-patch.md +1 -0
- package/prompts/read-guidelines.md +5 -5
- package/prompts/read.md +1 -1
- package/prompts/undo-last-edit-guidelines.md +2 -2
- package/prompts/undo-last-edit-snippet.md +1 -1
- package/prompts/undo-last-edit.md +1 -1
- package/src/apply-patch/commit.ts +190 -0
- package/src/apply-patch/grammar.ts +620 -0
- package/src/apply-patch/index.ts +17 -0
- package/src/apply-patch/lifecycle.ts +195 -0
- package/src/apply-patch/plan.ts +800 -0
- package/src/apply-patch/render.ts +137 -0
- package/src/apply-patch/resolve.ts +470 -0
- package/src/apply-patch/tool.ts +257 -0
- package/src/apply-patch/types.ts +387 -0
- package/src/cut-repair.ts +77 -11
- package/src/domain-errors.ts +58 -10
- package/src/edit-undo.ts +379 -25
- package/src/file-content/detection.ts +34 -0
- package/src/file-content/index.ts +2 -2
- package/src/file-content/preview.ts +1 -1
- package/src/file-content/served-header.ts +1 -1
- package/src/fs-lifecycle.ts +201 -0
- package/src/hash-store.ts +18 -1
- package/src/hashline/lease-resolve.ts +14 -8
- package/src/hashline/resolve.ts +30 -27
- package/src/host-utils.ts +224 -0
- package/src/mutation-engine/batch-span-gate.ts +17 -4
- package/src/mutation-engine/edit-request.ts +39 -0
- package/src/mutation-engine/pipeline.ts +535 -63
- package/src/mutation-engine/types.ts +49 -65
- package/src/mutation-queue.ts +1 -1
- package/src/prompts.ts +0 -28
- package/src/read-expansion/ast-provider.ts +4 -0
- package/src/read-expansion/grammar-manifest.ts +21 -2
- package/src/read-expansion/java-decoration.ts +34 -0
- package/src/read-expansion/span-expansion.ts +6 -2
- package/src/read-expansion/span-kind.ts +3 -1
- package/src/read-expansion/vue-decoration.ts +43 -0
- package/src/served-session/serve-render.ts +5 -5
- package/src/undo-store.ts +74 -5
- package/lib/types/dsh/encoding-bridge.d.ts +0 -25
- package/lib/types/dsh/encoding.d.ts +0 -144
- package/lib/types/dsh/tools/edit.d.ts +0 -10
- package/lib/types/src/edit-tool.d.ts +0 -41
- package/lib/types/src/mutation-engine/engine.d.ts +0 -31
- package/lib/types/src/payload-contract.d.ts +0 -124
- package/prompts/edit-guidelines.md +0 -10
- package/prompts/edit-snippet.md +0 -1
- package/prompts/edit.md +0 -1
- package/src/bash-classifier.ts +0 -1626
- package/src/bash-conversion/decide.ts +0 -812
- package/src/bash-display-transform.ts +0 -50
- package/src/bash-multifile-config.ts +0 -239
- package/src/bash-view-config.ts +0 -158
- package/src/edit-normalize.ts +0 -8
- package/src/edit-render.ts +0 -161
- package/src/edit-tool.ts +0 -116
- package/src/edit.ts +0 -136
- package/src/integrations/pi-lens/command.ts +0 -712
- package/src/integrations/pi-lens/config.ts +0 -148
- package/src/integrations/pi-lens/io-bridge-adapter.ts +0 -234
- package/src/integrations/pi-lens/io-bridge.ts +0 -112
- package/src/integrations/pi-lens/settings-overlay.ts +0 -272
- package/src/lifecycle-hooks/index.ts +0 -1992
- package/src/lifecycle-hooks/types.ts +0 -64
- package/src/mutated-files.ts +0 -75
- package/src/mutation-engine/engine.ts +0 -173
- package/src/mutation-engine/index.ts +0 -17
- package/src/payload-contract.ts +0 -637
- package/src/preview-controller.ts +0 -84
- package/src/read.ts +0 -865
- package/src/search-hook.ts +0 -41
- package/src/store-config.ts +0 -73
- package/src/store-janitor-warning.ts +0 -169
- package/src/store-janitor.ts +0 -539
- package/src/store-kernel/settings.ts +0 -233
- package/src/store.ts +0 -154
- package/src/tui-presenter.ts +0 -134
- 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 "
|
|
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 "
|
|
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. */
|