dsh-hashline-edit 0.1.0 → 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.
- package/README.md +72 -70
- package/cordis.patch.yml +1 -1
- package/lib/index.js +4494 -4813
- 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 +78 -0
- package/lib/types/dsh/host-compat.d.ts +7 -78
- package/lib/types/dsh/index.d.ts +17 -7
- 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 +65 -3
- 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/bash-view-banner.d.ts +20 -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 +10 -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
|
@@ -8,32 +8,49 @@
|
|
|
8
8
|
import type { LineEnding } from "../edit-diff.js";
|
|
9
9
|
import type { ResolvedRange } from "../served-session/serve-render.js";
|
|
10
10
|
import type { HashStore } from "../hash-store.js";
|
|
11
|
-
import type { EditDetails, RMetrics } from "../edit-response.js";
|
|
12
|
-
import type { DomainErrorCode, RangeCause } from "../domain-errors.js";
|
|
13
|
-
import type { ServedRow } from "../domain-errors.js";
|
|
14
11
|
|
|
15
12
|
// WHY: Re-export pipeline-facing options — validated once at admission (edit.ts),
|
|
16
13
|
// WHY: trusted inside the engine. No `any`.
|
|
17
14
|
export interface PipelineOptions {
|
|
15
|
+
/** Workspace root used to re-assert lifecycle path boundaries at commit time. */
|
|
16
|
+
workspaceRoot?: string;
|
|
18
17
|
accessMode?: number;
|
|
19
18
|
signal?: AbortSignal;
|
|
20
19
|
store?: HashStore;
|
|
21
20
|
noPersist?: boolean;
|
|
22
21
|
sessionKey?: string;
|
|
23
22
|
/**
|
|
24
|
-
* TEST-ONLY fault-injection seam (ticket-04b §3): invoked inside a
|
|
25
|
-
* AFTER the
|
|
26
|
-
*
|
|
27
|
-
* intent record and next-run
|
|
23
|
+
* TEST-ONLY fault-injection seam (ticket-04b §3): invoked inside a correlated write transaction
|
|
24
|
+
* AFTER the first file's rename is durably committed and BEFORE the next file's rename. A throw
|
|
25
|
+
* here lands inside the rename window — the cut path's target-then-sources order, or a multi-file
|
|
26
|
+
* edit's plan order — which is exactly the half-applied state the intent record and next-run
|
|
27
|
+
* repair must resolve.
|
|
28
28
|
*/
|
|
29
29
|
onCutBetweenWrites?: (committedAbsolutePath: string) => void | Promise<void>;
|
|
30
30
|
/**
|
|
31
|
-
* TEST-ONLY observation seam (ticket-04b remediation-2 R2): invoked inside a
|
|
32
|
-
* transaction BEFORE the first rename, while no byte
|
|
33
|
-
* under test is write-AHEAD: the intent record must
|
|
34
|
-
* first mutation — an existence check taken inside the
|
|
31
|
+
* TEST-ONLY observation seam (ticket-04b remediation-2 R2): invoked inside a correlated write
|
|
32
|
+
* transaction (a foreign-source cut, or a multi-file edit) BEFORE the first rename, while no byte
|
|
33
|
+
* of the transaction has landed. The rule under test is write-AHEAD: the intent record must
|
|
34
|
+
* already be durable at the moment of the first mutation — an existence check taken inside the
|
|
35
|
+
* window cannot show that.
|
|
35
36
|
*/
|
|
36
37
|
onBeforeFirstCutWrite?: () => void | Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* TEST-ONLY mid-member fault seam: called after a move's rename is durable and before its
|
|
40
|
+
* destination content is rewritten, so the rollback of that in-flight member is exercised.
|
|
41
|
+
*/
|
|
42
|
+
onAfterMoveRename?: (toAbsolutePath: string) => void | Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The honest outcome of a failed multi-member commit's rollback attempt. `restored` members are
|
|
47
|
+
* proven back at their pre-images; `remaining` members were not rolled back; `uncertain` members
|
|
48
|
+
* are those whose rollback attempt itself threw, so neither state can be claimed for them.
|
|
49
|
+
*/
|
|
50
|
+
export interface TransactionRecoveryReport {
|
|
51
|
+
restored: string[];
|
|
52
|
+
remaining: string[];
|
|
53
|
+
uncertain: string[];
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
/**
|
|
@@ -49,6 +66,24 @@ export interface ForeignCutRecord {
|
|
|
49
66
|
spanTo: string;
|
|
50
67
|
}
|
|
51
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The file-lifecycle action one transaction member carries.
|
|
71
|
+
*
|
|
72
|
+
* This is the engine-local vocabulary: apply_patch's plan IR maps onto it in
|
|
73
|
+
* `src/apply-patch/commit.ts` so the mutation engine never imports the apply-patch layer.
|
|
74
|
+
* Absent is an ordinary in-place edit.
|
|
75
|
+
*/
|
|
76
|
+
export type MemberLifecycle =
|
|
77
|
+
| { kind: "keep" }
|
|
78
|
+
| { kind: "add"; createdDirs: readonly string[] }
|
|
79
|
+
| { kind: "delete" }
|
|
80
|
+
| {
|
|
81
|
+
kind: "move";
|
|
82
|
+
fromAbsolutePath: string;
|
|
83
|
+
toAbsolutePath: string;
|
|
84
|
+
createdDirs: readonly string[];
|
|
85
|
+
};
|
|
86
|
+
|
|
52
87
|
// WHY: Internal: the engine's view of one file's mutation outcome.
|
|
53
88
|
// WHY: Mirrors `ProcessedEditFile` from the old pipeline — kept here as the engine's owned fact.
|
|
54
89
|
export interface ProcessedEditFile {
|
|
@@ -94,60 +129,9 @@ export interface ProcessedEditFile {
|
|
|
94
129
|
* correlated two-file transaction; the in-memory buffer itself never touches these files.
|
|
95
130
|
*/
|
|
96
131
|
foreignCuts: ForeignCutRecord[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// WHY: Discriminated success/failure for the deep seam.
|
|
100
|
-
// WHY: Callers use exhaustive switch on `ok` — no `isError` flag checks,
|
|
101
|
-
// WHY: no `any` threading.
|
|
102
|
-
export interface MutationSuccess {
|
|
103
|
-
ok: true;
|
|
104
|
-
/** SAFETY: Normalized result content (LF). */
|
|
105
|
-
result: string;
|
|
106
|
-
/** SAFETY: Unified diff (hash-anchored) for model consumption. */
|
|
107
|
-
diff: string;
|
|
108
|
-
/** SAFETY: User-facing drift notice, if any (details only, not model content). */
|
|
109
|
-
drift: string | undefined;
|
|
110
|
-
/** SAFETY: Metrics for telemetry. */
|
|
111
|
-
metrics: RMetrics;
|
|
112
|
-
raw: ProcessedEditFile;
|
|
113
|
-
/** SAFETY: Full tool result (content + details) for pi's tool_result hook. */
|
|
114
|
-
toolResult: {
|
|
115
|
-
content: Array<{ type: "text"; text: string }>;
|
|
116
|
-
details: EditDetails;
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface MutationFailure {
|
|
121
|
-
ok: false;
|
|
122
|
-
/**
|
|
123
|
-
* SAFETY: Machine code — a registry member (`DomainErrorCode`). `toFailure` validates every
|
|
124
|
-
* caught code through the envelope reader (`src/error-envelope.ts`): non-registry strings
|
|
125
|
-
* (errno pass-through included) route to `E_UNKNOWN`, so the union is honest.
|
|
126
|
-
*/
|
|
127
|
-
code: DomainErrorCode;
|
|
128
|
-
/** SAFETY: Human message — model-facing signal when applicable. */
|
|
129
|
-
message: string;
|
|
130
|
-
/** SAFETY: Fresh served block for retry when available (reject-and-serve). */
|
|
131
|
-
servedBlock?: string;
|
|
132
|
-
servedRows?: ServedRow[];
|
|
133
132
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* SAFETY: wording. Present only for `code: "E_BAD_PAYLOAD"`.
|
|
133
|
+
* apply_patch lifecycle action for this member; absent is an ordinary edit. The correlated
|
|
134
|
+
* transaction reads it for pre-image capture, commit ordering, rollback and undo-row shape.
|
|
137
135
|
*/
|
|
138
|
-
|
|
139
|
-
/** SAFETY: User-facing diagnosis, never a model remedy — a GLOSSARY.md glossary term. */
|
|
140
|
-
cause?: RangeCause;
|
|
141
|
-
details?: { code: DomainErrorCode; cause: RangeCause };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export type MutationResult = MutationSuccess | MutationFailure;
|
|
145
|
-
|
|
146
|
-
// WHY: Narrowing helpers — keep call sites exhaustive.
|
|
147
|
-
export function isMutationSuccess(r: MutationResult): r is MutationSuccess {
|
|
148
|
-
return r.ok === true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function isMutationFailure(r: MutationResult): r is MutationFailure {
|
|
152
|
-
return r.ok === false;
|
|
136
|
+
lifecycle?: MemberLifecycle;
|
|
153
137
|
}
|
package/src/mutation-queue.ts
CHANGED
package/src/prompts.ts
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { EDIT_DESCRIPTION, EDIT_SNIPPET, EDIT_GUIDELINES } from "./payload-contract.js";
|
|
3
|
-
|
|
4
|
-
const EDIT_PROMPT_FILES = new Set([
|
|
5
|
-
"../prompts/edit.md",
|
|
6
|
-
"prompts/edit.md",
|
|
7
|
-
"../prompts/edit-snippet.md",
|
|
8
|
-
"prompts/edit-snippet.md",
|
|
9
|
-
]);
|
|
10
|
-
|
|
11
|
-
const EDIT_GUIDE_FILES = new Set(["../prompts/edit-guidelines.md", "prompts/edit-guidelines.md"]);
|
|
12
2
|
|
|
13
3
|
export function loadP(relativePath: string, replacements?: Record<string, string>): string {
|
|
14
|
-
if (EDIT_PROMPT_FILES.has(relativePath)) {
|
|
15
|
-
let content = relativePath.includes("snippet") ? EDIT_SNIPPET : EDIT_DESCRIPTION;
|
|
16
|
-
if (replacements) {
|
|
17
|
-
for (const [key, value] of Object.entries(replacements)) {
|
|
18
|
-
content = content.split(`{{${key}}}`).join(value);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return content;
|
|
22
|
-
}
|
|
23
4
|
let content = readFileSync(new URL(relativePath, import.meta.url), "utf-8").trim();
|
|
24
5
|
if (replacements) {
|
|
25
6
|
for (const [key, value] of Object.entries(replacements)) {
|
|
@@ -30,15 +11,6 @@ export function loadP(relativePath: string, replacements?: Record<string, string
|
|
|
30
11
|
}
|
|
31
12
|
|
|
32
13
|
export function loadGuide(relativePath: string, replacements?: Record<string, string>): string[] {
|
|
33
|
-
if (EDIT_GUIDE_FILES.has(relativePath)) {
|
|
34
|
-
let guidelines = [...EDIT_GUIDELINES];
|
|
35
|
-
if (replacements) {
|
|
36
|
-
for (const [key, value] of Object.entries(replacements)) {
|
|
37
|
-
guidelines = guidelines.map((line) => line.split(`{{${key}}}`).join(value));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return guidelines;
|
|
41
|
-
}
|
|
42
14
|
let content = readFileSync(new URL(relativePath, import.meta.url), "utf-8");
|
|
43
15
|
if (replacements) {
|
|
44
16
|
for (const [key, value] of Object.entries(replacements)) {
|
|
@@ -24,6 +24,8 @@ import { TYPESCRIPT_DECORATION } from "./typescript-decoration.js";
|
|
|
24
24
|
import { PYTHON_DECORATION } from "./python-decoration.js";
|
|
25
25
|
import { GO_DECORATION } from "./go-decoration.js";
|
|
26
26
|
import { RUST_DECORATION } from "./rust-decoration.js";
|
|
27
|
+
import { JAVA_DECORATION } from "./java-decoration.js";
|
|
28
|
+
import { VUE_DECORATION } from "./vue-decoration.js";
|
|
27
29
|
|
|
28
30
|
interface LanguageScope {
|
|
29
31
|
readonly decorate: ScopeDecorator;
|
|
@@ -45,6 +47,8 @@ export const LANGUAGES: Readonly<Record<string, LanguageScope>> = {
|
|
|
45
47
|
},
|
|
46
48
|
go: { decorate: createScopeDecorator(GO_DECORATION), kinds: GO_DECORATION.kinds },
|
|
47
49
|
rust: { decorate: createScopeDecorator(RUST_DECORATION), kinds: RUST_DECORATION.kinds },
|
|
50
|
+
java: { decorate: createScopeDecorator(JAVA_DECORATION), kinds: JAVA_DECORATION.kinds },
|
|
51
|
+
vue: { decorate: createScopeDecorator(VUE_DECORATION), kinds: VUE_DECORATION.kinds },
|
|
48
52
|
};
|
|
49
53
|
|
|
50
54
|
export interface AstSpanProviderOptions extends GrammarCacheOptions {
|
|
@@ -19,8 +19,10 @@ export interface GrammarManifestEntry {
|
|
|
19
19
|
export type GrammarManifest = Readonly<Record<string, GrammarManifestEntry>>;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* TypeScript/TSX
|
|
23
|
-
*
|
|
22
|
+
* Pinned grammars: TypeScript/TSX from the tree-sitter-typescript release, Python/Go/Rust from their
|
|
23
|
+
* own releases, Java from the tree-sitter-java release and Vue from the tree-sitter-vue-wasm npm
|
|
24
|
+
* package. Every entry is fetched from a versioned URL and verified against the committed digest
|
|
25
|
+
* before it reaches the parser.
|
|
24
26
|
*/
|
|
25
27
|
export const GRAMMAR_MANIFEST: GrammarManifest = {
|
|
26
28
|
typescript: {
|
|
@@ -53,6 +55,18 @@ export const GRAMMAR_MANIFEST: GrammarManifest = {
|
|
|
53
55
|
url: "https://github.com/tree-sitter/tree-sitter-rust/releases/download/v0.24.2/tree-sitter-rust.wasm",
|
|
54
56
|
sha256: "24c89bd9252255e4aebbcbd7d2d308bd92c86dd95a130fdc80efa49577b8d738",
|
|
55
57
|
},
|
|
58
|
+
java: {
|
|
59
|
+
language: "java",
|
|
60
|
+
version: "0.23.5",
|
|
61
|
+
url: "https://github.com/tree-sitter/tree-sitter-java/releases/download/v0.23.5/tree-sitter-java.wasm",
|
|
62
|
+
sha256: "4fdeac4ca6ca089f06c6f7e562abcac1733cd465728cc7031ebb73c2019122c4",
|
|
63
|
+
},
|
|
64
|
+
vue: {
|
|
65
|
+
language: "vue",
|
|
66
|
+
version: "0.1.0",
|
|
67
|
+
url: "https://unpkg.com/tree-sitter-vue-wasm@0.1.0/tree-sitter-vue.wasm",
|
|
68
|
+
sha256: "193c01119229352f1a772952e590b2ad59a75ee21eb7244433abdafdaacaea71",
|
|
69
|
+
},
|
|
56
70
|
};
|
|
57
71
|
|
|
58
72
|
const LANGUAGE_BY_EXTENSION = new Map([
|
|
@@ -63,6 +77,11 @@ const LANGUAGE_BY_EXTENSION = new Map([
|
|
|
63
77
|
[".py", "python"],
|
|
64
78
|
[".go", "go"],
|
|
65
79
|
[".rs", "rust"],
|
|
80
|
+
[".java", "java"],
|
|
81
|
+
[".vue", "vue"],
|
|
82
|
+
// WHY: no .sh/.bash here — bash-provider.ts already serves shell functions through the existing
|
|
83
|
+
// WHY: `unbash` dependency (ADR-0008 decision 10); mapping them here would prefetch a second,
|
|
84
|
+
// WHY: unused parser and contradict the no-new-download decision.
|
|
66
85
|
]);
|
|
67
86
|
|
|
68
87
|
/** The manifest entry for a source path, or undefined when no grammar is supported yet. */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Java vocabulary for the shared scope-decoration walk. */
|
|
2
|
+
import type { DecorationSpec } from "./scope-decoration.js";
|
|
3
|
+
import type { SpanKindTable } from "./span-kind.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Java declaration nodes and their note kinds. A record is a class-like declaration, so it shares
|
|
7
|
+
* the `class` label; a constructor is its own protocol kind and is never relabelled as a method.
|
|
8
|
+
*/
|
|
9
|
+
export const JAVA_SPAN_KINDS = {
|
|
10
|
+
class_declaration: "class",
|
|
11
|
+
record_declaration: "class",
|
|
12
|
+
interface_declaration: "interface",
|
|
13
|
+
enum_declaration: "enum",
|
|
14
|
+
method_declaration: "method",
|
|
15
|
+
constructor_declaration: "constructor",
|
|
16
|
+
compact_constructor_declaration: "constructor",
|
|
17
|
+
} as const satisfies SpanKindTable;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A Java declaration carries its modifiers (annotations included) inside its own node, so the
|
|
21
|
+
* span already starts at the first annotation. Only a directly preceding JavaDoc block needs the
|
|
22
|
+
* leading-sibling walk; ordinary `//` and `/*` comments are not documentation and stay outside.
|
|
23
|
+
*/
|
|
24
|
+
export const JAVA_DECORATION: DecorationSpec = {
|
|
25
|
+
kinds: JAVA_SPAN_KINDS,
|
|
26
|
+
docCommentTypes: new Set(["block_comment"]),
|
|
27
|
+
isDocComment: (node) => node.text?.trimStart().startsWith("/**") ?? false,
|
|
28
|
+
wrapperTypes: new Set(),
|
|
29
|
+
assignmentNodeTypes: new Set(),
|
|
30
|
+
assignmentWrapperTypes: new Set(),
|
|
31
|
+
transparentTypes: new Set(),
|
|
32
|
+
decorationTypes: new Set(),
|
|
33
|
+
nameField: "name",
|
|
34
|
+
};
|
|
@@ -62,9 +62,13 @@ export interface SpanProvider {
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Only a folded requested span at or below this size triggers expansion. Folding happens first, so
|
|
65
|
-
* sixteen adjacent
|
|
65
|
+
* sixteen adjacent windows are one span and refuse the trigger.
|
|
66
|
+
*
|
|
67
|
+
* WHY 61: measured against the working corpus — 25-40 line reads occurred 66 times, 41-60 38 times,
|
|
68
|
+
* 61-100 38 times and >200 once, with a median refused read of 61 lines. At the previous 24-line
|
|
69
|
+
* trigger expansion engaged on only 9.6% of reads; the cap, not this trigger, still bounds the cost.
|
|
66
70
|
*/
|
|
67
|
-
export const TRIGGER_MAX =
|
|
71
|
+
export const TRIGGER_MAX = 61;
|
|
68
72
|
|
|
69
73
|
/** Code scopes may widen to a function or method; this ceiling is refused, never clamped. */
|
|
70
74
|
export const SPAN_CAP_CODE = 80;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* `SPAN_KIND_TO_LSP` carries the canonical PascalCase name next to it. Six extensions cover
|
|
6
6
|
* concepts the protocol cannot express honestly: `arrow` (an anonymous function assigned to a
|
|
7
7
|
* name), `typeAlias`, `impl` and `trait` (Rust items; mapping them to `Object` would be a lie),
|
|
8
|
-
* and the non-
|
|
8
|
+
* and the non-symbol document units `section` (Markdown sections and Vue SFC blocks) and
|
|
9
|
+
* `paragraph` (plain-text paragraphs).
|
|
9
10
|
*
|
|
10
11
|
* Display stays lowercase because the note is model-facing prose, not a protocol symbol. The two
|
|
11
12
|
* camel-case members `enumMember` and `typeParameter` are rendered as `enum member` and
|
|
@@ -187,6 +188,7 @@ export const PRODUCED_KINDS = [
|
|
|
187
188
|
"typeAlias",
|
|
188
189
|
"section",
|
|
189
190
|
"paragraph",
|
|
191
|
+
"constructor",
|
|
190
192
|
] as const satisfies readonly SpanKind[];
|
|
191
193
|
|
|
192
194
|
/** One language's grammar-node-type to note-kind data table. */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Vue single-file-component vocabulary for the shared scope-decoration walk. */
|
|
2
|
+
import type { SyntaxNodeLike } from "./ast-span.js";
|
|
3
|
+
import type { DecorationSpec } from "./scope-decoration.js";
|
|
4
|
+
import type { SpanKindTable } from "./span-kind.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A `.vue` file is one component made of top-level document sections. The note vocabulary has no
|
|
8
|
+
* template/style symbol and `component` is the file root (never a widening target), so the three
|
|
9
|
+
* SFC block nodes share the existing `section` extension and are distinguished by their names.
|
|
10
|
+
* Embedded JavaScript/TypeScript is raw text in this grammar, so a script section is never parsed
|
|
11
|
+
* or widened beyond its own block.
|
|
12
|
+
*/
|
|
13
|
+
export const VUE_SPAN_KINDS = {
|
|
14
|
+
template_element: "section",
|
|
15
|
+
script_element: "section",
|
|
16
|
+
style_element: "section",
|
|
17
|
+
} as const satisfies SpanKindTable;
|
|
18
|
+
|
|
19
|
+
/** The block name a reader sees after the `section` label: template, script or style. */
|
|
20
|
+
function vueSectionName(node: SyntaxNodeLike): string | undefined {
|
|
21
|
+
switch (node.type) {
|
|
22
|
+
case "template_element":
|
|
23
|
+
return "template";
|
|
24
|
+
case "script_element":
|
|
25
|
+
return "script";
|
|
26
|
+
case "style_element":
|
|
27
|
+
return "style";
|
|
28
|
+
default:
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const VUE_DECORATION: DecorationSpec = {
|
|
34
|
+
kinds: VUE_SPAN_KINDS,
|
|
35
|
+
docCommentTypes: new Set(),
|
|
36
|
+
wrapperTypes: new Set(),
|
|
37
|
+
assignmentNodeTypes: new Set(),
|
|
38
|
+
assignmentWrapperTypes: new Set(),
|
|
39
|
+
transparentTypes: new Set(),
|
|
40
|
+
decorationTypes: new Set(),
|
|
41
|
+
nameField: "name",
|
|
42
|
+
nameOf: vueSectionName,
|
|
43
|
+
};
|
|
@@ -90,7 +90,7 @@ export function fmtServedRows(rows: ServedRow[], fileLines: string[]): string {
|
|
|
90
90
|
|
|
91
91
|
// WHY: the one continuation a rejected edit offers (read-chrome): the `windows` offset, not a bare
|
|
92
92
|
// WHY: `offset`, because `offset` alone no longer continues anything a page footer used to name.
|
|
93
|
-
function paginationHint(nextOffset: number, more: number): string {
|
|
93
|
+
export function paginationHint(nextOffset: number, more: number): string {
|
|
94
94
|
return `[... ${more} more — use windows: [{ offset: ${nextOffset} }]]`;
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -107,7 +107,7 @@ export function servedPositionsOf(served: (string | null)[], hash: string): numb
|
|
|
107
107
|
// WHY: ---------------------------------------------------------------------------
|
|
108
108
|
|
|
109
109
|
/** Builds the reject-and-serve served rows + rendered block for a current on-disk range. */
|
|
110
|
-
function buildRangeServeBlock(
|
|
110
|
+
export function buildRangeServeBlock(
|
|
111
111
|
startLine: number,
|
|
112
112
|
endLine: number,
|
|
113
113
|
fileHashes: string[],
|
|
@@ -221,9 +221,9 @@ export function makeServedRejection(opts: {
|
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
223
|
* Builds the `[E_STALE_ANCHOR]` reject-and-serve rejection for a boundary anchor the session holds
|
|
224
|
-
* no lease for. It serves the rows under `Current range:` with
|
|
225
|
-
* served rows are themselves serves, so
|
|
226
|
-
* codes,
|
|
224
|
+
* no lease for. It serves the rows under `Current range:` as evidence with no retry mandate: the
|
|
225
|
+
* served rows are themselves serves, so deciding from them needs no `read` (same fresh-read rule as
|
|
226
|
+
* the range-family codes, issue #149). The registry renders the block at most once.
|
|
227
227
|
*/
|
|
228
228
|
export function makeStaleAnchorRejection(opts: {
|
|
229
229
|
headline: string;
|
package/src/undo-store.ts
CHANGED
|
@@ -29,8 +29,25 @@ export interface UndoRecord {
|
|
|
29
29
|
* `null` is a row from before this column, or an ordinary single-file edit.
|
|
30
30
|
*/
|
|
31
31
|
rawPre?: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* The lifecycle action this row reverses. Absent/null is an ordinary edit (back-compatible).
|
|
34
|
+
* add = the file did not exist before; delete = the file does not exist after; move = a pure
|
|
35
|
+
* rename with an optional content edit.
|
|
36
|
+
*/
|
|
37
|
+
lifecycle?: UndoLifecycle | null;
|
|
38
|
+
/** Move: the absolute path the file had before the transaction. */
|
|
39
|
+
fromPath?: string | null;
|
|
40
|
+
/** Move: the absolute path the file has after the transaction. */
|
|
41
|
+
toPath?: string | null;
|
|
42
|
+
/** The RAW post-transaction text when the commit wrote non-canonical bytes (pure move). */
|
|
43
|
+
rawPost?: string | null;
|
|
44
|
+
/** Add: the directories the add created, outermost first, JSON-encoded in the column. */
|
|
45
|
+
createdDirs?: readonly string[] | null;
|
|
32
46
|
}
|
|
33
47
|
|
|
48
|
+
/** The lifecycle action one undo row reverses. */
|
|
49
|
+
export type UndoLifecycle = "edit" | "add" | "delete" | "move";
|
|
50
|
+
|
|
34
51
|
export type CutIntentDirection = "revert" | null;
|
|
35
52
|
|
|
36
53
|
export interface CutIntent {
|
|
@@ -50,6 +67,11 @@ export interface UndoStmts {
|
|
|
50
67
|
snapshotHash: string | null,
|
|
51
68
|
transactionId: string | null,
|
|
52
69
|
rawPre: string | null,
|
|
70
|
+
lifecycle: string | null,
|
|
71
|
+
fromPath: string | null,
|
|
72
|
+
toPath: string | null,
|
|
73
|
+
rawPost: string | null,
|
|
74
|
+
createdDirs: string | null,
|
|
53
75
|
anchorGeneration: number,
|
|
54
76
|
updatedAt: number,
|
|
55
77
|
) => void;
|
|
@@ -75,15 +97,15 @@ export function undoStmts(db: DatabaseSync): UndoStmts {
|
|
|
75
97
|
|
|
76
98
|
function buildStmts(db: DatabaseSync): UndoStmts {
|
|
77
99
|
const undoUpsertStmt = db.prepare(
|
|
78
|
-
"INSERT INTO file_undo (path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, anchor_generation, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
|
79
|
-
"ON CONFLICT(path) DO UPDATE SET content = excluded.content, bom = excluded.bom, ending = excluded.ending, hashes = excluded.hashes, result_content = excluded.result_content, snapshot_hash = excluded.snapshot_hash, transaction_id = excluded.transaction_id, raw_pre = excluded.raw_pre, anchor_generation = excluded.anchor_generation, updated_at = excluded.updated_at",
|
|
100
|
+
"INSERT INTO file_undo (path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, lifecycle, from_path, to_path, raw_post, created_dirs, anchor_generation, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
|
101
|
+
"ON CONFLICT(path) DO UPDATE SET content = excluded.content, bom = excluded.bom, ending = excluded.ending, hashes = excluded.hashes, result_content = excluded.result_content, snapshot_hash = excluded.snapshot_hash, transaction_id = excluded.transaction_id, raw_pre = excluded.raw_pre, lifecycle = excluded.lifecycle, from_path = excluded.from_path, to_path = excluded.to_path, raw_post = excluded.raw_post, created_dirs = excluded.created_dirs, anchor_generation = excluded.anchor_generation, updated_at = excluded.updated_at",
|
|
80
102
|
);
|
|
81
103
|
const undoGetStmt = db.prepare(
|
|
82
|
-
"SELECT content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, anchor_generation FROM file_undo WHERE path = ?",
|
|
104
|
+
"SELECT content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, lifecycle, from_path, to_path, raw_post, created_dirs, anchor_generation FROM file_undo WHERE path = ?",
|
|
83
105
|
);
|
|
84
106
|
const undoDelStmt = db.prepare("DELETE FROM file_undo WHERE path = ?");
|
|
85
107
|
const undoGetTransactionStmt = db.prepare(
|
|
86
|
-
"SELECT path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, anchor_generation FROM file_undo WHERE transaction_id = ? ORDER BY path",
|
|
108
|
+
"SELECT path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, lifecycle, from_path, to_path, raw_post, created_dirs, anchor_generation FROM file_undo WHERE transaction_id = ? ORDER BY path",
|
|
87
109
|
);
|
|
88
110
|
const undoDelTransactionStmt = db.prepare("DELETE FROM file_undo WHERE transaction_id = ?");
|
|
89
111
|
const intentUpsertStmt = db.prepare(
|
|
@@ -105,6 +127,11 @@ function buildStmts(db: DatabaseSync): UndoStmts {
|
|
|
105
127
|
snapshotHash,
|
|
106
128
|
transactionId,
|
|
107
129
|
rawPre,
|
|
130
|
+
lifecycle,
|
|
131
|
+
fromPath,
|
|
132
|
+
toPath,
|
|
133
|
+
rawPost,
|
|
134
|
+
createdDirs,
|
|
108
135
|
anchorGeneration,
|
|
109
136
|
updatedAt,
|
|
110
137
|
) => {
|
|
@@ -119,6 +146,11 @@ function buildStmts(db: DatabaseSync): UndoStmts {
|
|
|
119
146
|
snapshotHash,
|
|
120
147
|
transactionId,
|
|
121
148
|
rawPre,
|
|
149
|
+
lifecycle,
|
|
150
|
+
fromPath,
|
|
151
|
+
toPath,
|
|
152
|
+
rawPost,
|
|
153
|
+
createdDirs,
|
|
122
154
|
anchorGeneration,
|
|
123
155
|
updatedAt,
|
|
124
156
|
);
|
|
@@ -164,12 +196,25 @@ onStoreOpen((db) => {
|
|
|
164
196
|
ensureFileUndoSchema(db);
|
|
165
197
|
});
|
|
166
198
|
|
|
199
|
+
/** Parse the JSON-encoded created-directory list; a corrupt value degrades to an empty list. */
|
|
200
|
+
function parseCreatedDirs(value: unknown): string[] | null {
|
|
201
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
202
|
+
try {
|
|
203
|
+
const parsed: unknown = JSON.parse(value);
|
|
204
|
+
return Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")
|
|
205
|
+
? (parsed as string[])
|
|
206
|
+
: null;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
167
212
|
function parseUndoRow(row: Record<string, unknown> | undefined): UndoRecord | undefined {
|
|
168
213
|
if (!row) return undefined;
|
|
169
214
|
try {
|
|
170
215
|
const parsed = JSON.parse(row.hashes as string);
|
|
171
216
|
if (!isValidHashList(parsed)) return undefined;
|
|
172
|
-
|
|
217
|
+
const record: UndoRecord = {
|
|
173
218
|
content: row.content as string,
|
|
174
219
|
bom: row.bom as string,
|
|
175
220
|
ending: row.ending as string,
|
|
@@ -180,6 +225,23 @@ function parseUndoRow(row: Record<string, unknown> | undefined): UndoRecord | un
|
|
|
180
225
|
rawPre: (row.raw_pre as string | null) ?? null,
|
|
181
226
|
anchorGeneration: (row.anchor_generation as number | null) ?? 0,
|
|
182
227
|
};
|
|
228
|
+
// WHY: the lifecycle fields are additive; an ordinary row omits them entirely so its
|
|
229
|
+
// WHY: pre-extension shape stays byte-for-byte what existing consumers assert.
|
|
230
|
+
if (row.lifecycle !== null && row.lifecycle !== undefined) {
|
|
231
|
+
record.lifecycle = row.lifecycle as UndoLifecycle;
|
|
232
|
+
}
|
|
233
|
+
if (row.from_path !== null && row.from_path !== undefined) {
|
|
234
|
+
record.fromPath = row.from_path as string;
|
|
235
|
+
}
|
|
236
|
+
if (row.to_path !== null && row.to_path !== undefined) {
|
|
237
|
+
record.toPath = row.to_path as string;
|
|
238
|
+
}
|
|
239
|
+
if (row.raw_post !== null && row.raw_post !== undefined) {
|
|
240
|
+
record.rawPost = row.raw_post as string;
|
|
241
|
+
}
|
|
242
|
+
const createdDirs = parseCreatedDirs(row.created_dirs);
|
|
243
|
+
if (createdDirs !== null) record.createdDirs = createdDirs;
|
|
244
|
+
return record;
|
|
183
245
|
} catch {
|
|
184
246
|
return undefined;
|
|
185
247
|
}
|
|
@@ -196,6 +258,13 @@ export function upsertUndo(store: HashStore, path: string, entry: UndoRecord): v
|
|
|
196
258
|
entry.snapshotHash ?? null,
|
|
197
259
|
entry.transactionId ?? null,
|
|
198
260
|
entry.rawPre ?? null,
|
|
261
|
+
entry.lifecycle ?? null,
|
|
262
|
+
entry.fromPath ?? null,
|
|
263
|
+
entry.toPath ?? null,
|
|
264
|
+
entry.rawPost ?? null,
|
|
265
|
+
entry.createdDirs === null || entry.createdDirs === undefined
|
|
266
|
+
? null
|
|
267
|
+
: JSON.stringify(entry.createdDirs),
|
|
199
268
|
// WHY: ADR-0031 §4 (refuse, do not launder) — the stamp comes from the payload, never
|
|
200
269
|
// WHY: hard-coded: a replay of a pre-generation row preserves its generation so the
|
|
201
270
|
// WHY: restore gate re-derives, while a fresh write (absent generation) defaults current.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { FileIO } from "./contracts.js";
|
|
2
|
-
import { type EncodingState } from "./encoding.js";
|
|
3
|
-
/** One staged UTF-8 bridge: the bytes to restore on failure, the state to invert on success. */
|
|
4
|
-
export interface EncodingBridge {
|
|
5
|
-
readonly absolutePath: string;
|
|
6
|
-
readonly rawBytes: Uint8Array;
|
|
7
|
-
readonly state: EncodingState;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Capture every member of one mutation (a correlated cut's target and source
|
|
11
|
-
* files), staging each recorded legacy/UTF-16 member as UTF-8. Members without a
|
|
12
|
-
* recorded state or with a UTF-8-native state are skipped, so they keep the
|
|
13
|
-
* core's UTF-8 behavior. Staging is all-or-nothing: a mid-list failure restores
|
|
14
|
-
* the members already staged before rethrowing.
|
|
15
|
-
* @param files - the mutation's member paths, in commit order.
|
|
16
|
-
* @param label - operation name for diagnostics and rollback messages.
|
|
17
|
-
*/
|
|
18
|
-
export declare function captureEncodingBridges(io: FileIO, files: readonly string[], cwd: string, signal: AbortSignal | undefined, label: string): Promise<EncodingBridge[]>;
|
|
19
|
-
/**
|
|
20
|
-
* Invert the recorded encoding over each member's core-written UTF-8 result and
|
|
21
|
-
* collect the model-facing warning of every lossy degradation.
|
|
22
|
-
*/
|
|
23
|
-
export declare function finishEncodingBridges(io: FileIO, bridges: readonly EncodingBridge[], label: string): Promise<string[]>;
|
|
24
|
-
/** Restore every captured member's pre-mutation bytes after a refusal (best-effort). */
|
|
25
|
-
export declare function restoreEncodingBridges(io: FileIO, bridges: readonly EncodingBridge[], label: string): Promise<void>;
|