@codyswann/lisa 4.17.19 → 4.18.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/dist/cli/ui-config-write-document.d.ts +53 -0
- package/dist/cli/ui-config-write-document.d.ts.map +1 -0
- package/dist/cli/ui-config-write-document.js +212 -0
- package/dist/cli/ui-config-write-document.js.map +1 -0
- package/dist/cli/ui-config-write-patch-plan.d.ts +30 -0
- package/dist/cli/ui-config-write-patch-plan.d.ts.map +1 -0
- package/dist/cli/ui-config-write-patch-plan.js +151 -0
- package/dist/cli/ui-config-write-patch-plan.js.map +1 -0
- package/dist/cli/ui-config-write-persistence.d.ts +19 -0
- package/dist/cli/ui-config-write-persistence.d.ts.map +1 -0
- package/dist/cli/ui-config-write-persistence.js +294 -0
- package/dist/cli/ui-config-write-persistence.js.map +1 -0
- package/dist/cli/ui-config-write-property-edits.d.ts +40 -0
- package/dist/cli/ui-config-write-property-edits.d.ts.map +1 -0
- package/dist/cli/ui-config-write-property-edits.js +250 -0
- package/dist/cli/ui-config-write-property-edits.js.map +1 -0
- package/dist/cli/ui-config-write-provenance.d.ts +12 -0
- package/dist/cli/ui-config-write-provenance.d.ts.map +1 -0
- package/dist/cli/ui-config-write-provenance.js +29 -0
- package/dist/cli/ui-config-write-provenance.js.map +1 -0
- package/dist/cli/ui-config-write-root-identity.d.ts +25 -0
- package/dist/cli/ui-config-write-root-identity.d.ts.map +1 -0
- package/dist/cli/ui-config-write-root-identity.js +66 -0
- package/dist/cli/ui-config-write-root-identity.js.map +1 -0
- package/dist/cli/ui-config-write.d.ts.map +1 -1
- package/dist/cli/ui-config-write.js +47 -92
- package/dist/cli/ui-config-write.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +7 -1
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/sync/json-path.d.ts +10 -0
- package/dist/sync/json-path.d.ts.map +1 -1
- package/dist/sync/json-path.js +14 -0
- package/dist/sync/json-path.js.map +1 -1
- package/dist/sync/sync-population.d.ts +6 -0
- package/dist/sync/sync-population.d.ts.map +1 -1
- package/dist/sync/sync-population.js +8 -0
- package/dist/sync/sync-population.js.map +1 -1
- package/dist/utils/atomic-file-write.d.ts +5 -4
- package/dist/utils/atomic-file-write.d.ts.map +1 -1
- package/dist/utils/atomic-file-write.js +3 -0
- package/dist/utils/atomic-file-write.js.map +1 -1
- package/package.json +2 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/ui/README.md +62 -8
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ui-config-write-persistence.ts
|
|
3
|
+
* @description Safe, surgical persistence for localhost UI config writes.
|
|
4
|
+
*
|
|
5
|
+
* Requests are serialized across processes because both config files are
|
|
6
|
+
* shared read-modify-write state. Every request snapshots, reconciles, and
|
|
7
|
+
* validates both files before publishing either one. Canonical-path and
|
|
8
|
+
* filesystem-identity checks fail closed when a project root or ancestor is
|
|
9
|
+
* replaced at a checked boundary. Node has no portable descriptor-relative
|
|
10
|
+
* temp-create/rename API, so a same-user swap inside the final check-to-use gap
|
|
11
|
+
* remains possible. The two final renames also cannot form one filesystem
|
|
12
|
+
* transaction, so a failure between them can leave the first target published.
|
|
13
|
+
* @module cli/ui-config-write-persistence
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { lstat, mkdir, open, realpath, } from "node:fs/promises";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { withFileTargetLock } from "../core/learnings-lock.js";
|
|
20
|
+
import { writeFileAtomically } from "../utils/atomic-file-write.js";
|
|
21
|
+
import { parseConfigDocument, renderConfigChanges, } from "./ui-config-write-document.js";
|
|
22
|
+
import { assertProjectRootIdentity, requireCanonicalProjectRoot, } from "./ui-config-write-root-identity.js";
|
|
23
|
+
import { provenanceRemovals } from "./ui-config-write-provenance.js";
|
|
24
|
+
const CONFIG_FILE = ".lisa.config.json";
|
|
25
|
+
const LOCAL_CONFIG_FILE = ".lisa.config.local.json";
|
|
26
|
+
const MAX_CONFIG_BYTES = 128 * 1024;
|
|
27
|
+
const READ_CHUNK_BYTES = 16 * 1024;
|
|
28
|
+
const WRITE_PERMISSION_BITS = 0o222;
|
|
29
|
+
const LOCK_DIRECTORY_PRIVATE_BITS = 0o077;
|
|
30
|
+
const LOCK_DIRECTORY_MODE = 0o700;
|
|
31
|
+
const CONFIG_LOCK_DIRECTORY = path.join(tmpdir(), `lisa-ui-config-write-${process.getuid?.() ?? "user"}`);
|
|
32
|
+
const configWriteQueues = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Persist one pre-classified request under the project write queue.
|
|
35
|
+
* @param destDir - Project root containing the two fixed config filenames
|
|
36
|
+
* @param changes - Changes split by committed versus local ownership
|
|
37
|
+
* @param validateCommitted - Registry validation applied before either write
|
|
38
|
+
* @returns Prospective committed config only; local values are never echoed
|
|
39
|
+
*/
|
|
40
|
+
export async function persistRoutedConfigChanges(destDir, changes, validateCommitted) {
|
|
41
|
+
const projectRoot = await requireCanonicalProjectRoot(destDir);
|
|
42
|
+
const lockTarget = await resolveConfigLockTarget(projectRoot.path);
|
|
43
|
+
return await withConfigWriteLock(projectRoot.path, async () => await withFileTargetLock(lockTarget, async () => {
|
|
44
|
+
await assertProjectRootIdentity(projectRoot);
|
|
45
|
+
const committedTarget = path.join(projectRoot.path, CONFIG_FILE);
|
|
46
|
+
const localTarget = path.join(projectRoot.path, LOCAL_CONFIG_FILE);
|
|
47
|
+
const [committedSnapshot, localSnapshot] = await Promise.all([
|
|
48
|
+
readConfigSnapshot(committedTarget, CONFIG_FILE, projectRoot),
|
|
49
|
+
readConfigSnapshot(localTarget, LOCAL_CONFIG_FILE, projectRoot),
|
|
50
|
+
]);
|
|
51
|
+
await assertProjectRootIdentity(projectRoot);
|
|
52
|
+
const committed = prepareConfig(committedSnapshot, Object.keys(changes.local), changes.committed, provenanceRemovals(changes.committed));
|
|
53
|
+
const local = prepareConfig(localSnapshot, Object.keys(changes.committed), changes.local);
|
|
54
|
+
assertPreparedSize(committed);
|
|
55
|
+
assertPreparedSize(local);
|
|
56
|
+
validateCommitted(committed.document);
|
|
57
|
+
await assertProjectRootIdentity(projectRoot);
|
|
58
|
+
assertWritableWhenChanged(committed);
|
|
59
|
+
assertWritableWhenChanged(local);
|
|
60
|
+
await publishPrepared(committed, false, projectRoot);
|
|
61
|
+
await assertProjectRootIdentity(projectRoot);
|
|
62
|
+
await publishPrepared(local, true, projectRoot);
|
|
63
|
+
await assertProjectRootIdentity(projectRoot);
|
|
64
|
+
return committed.document;
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Derive a private, repository-external lock identity from the canonical root.
|
|
69
|
+
*
|
|
70
|
+
* The lock cannot live beside either config: the committed file would leave
|
|
71
|
+
* transient untracked state, while the local file's exact ignore rule does not
|
|
72
|
+
* cover an adjacent `.lock`. A user-private temp directory keeps lock metadata
|
|
73
|
+
* outside version control; hashing avoids publishing project paths there.
|
|
74
|
+
* @param projectRoot - Canonical project directory
|
|
75
|
+
* @returns Hardened lock primitive target shared by every alias and process
|
|
76
|
+
*/
|
|
77
|
+
async function resolveConfigLockTarget(projectRoot) {
|
|
78
|
+
await mkdir(CONFIG_LOCK_DIRECTORY, {
|
|
79
|
+
recursive: true,
|
|
80
|
+
mode: LOCK_DIRECTORY_MODE,
|
|
81
|
+
});
|
|
82
|
+
const metadata = await lstat(CONFIG_LOCK_DIRECTORY);
|
|
83
|
+
const currentUid = process.getuid?.();
|
|
84
|
+
if (!metadata.isDirectory() ||
|
|
85
|
+
metadata.isSymbolicLink() ||
|
|
86
|
+
(currentUid !== undefined && metadata.uid !== currentUid) ||
|
|
87
|
+
(process.platform !== "win32" &&
|
|
88
|
+
(metadata.mode & LOCK_DIRECTORY_PRIVATE_BITS) !== 0)) {
|
|
89
|
+
throw new Error("Unsafe UI config lock directory");
|
|
90
|
+
}
|
|
91
|
+
const canonicalLockDirectory = await realpath(CONFIG_LOCK_DIRECTORY);
|
|
92
|
+
const identity = createHash("sha256").update(projectRoot).digest("hex");
|
|
93
|
+
return path.join(canonicalLockDirectory, identity);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Serialize whole mixed-file transactions per canonical project root.
|
|
97
|
+
* @param projectRoot - Absolute project root used as the queue identity
|
|
98
|
+
* @param operation - Complete snapshot, validate, and publish transaction
|
|
99
|
+
* @returns Operation result after all earlier writes settle
|
|
100
|
+
*/
|
|
101
|
+
async function withConfigWriteLock(projectRoot, operation) {
|
|
102
|
+
const previous = configWriteQueues.get(projectRoot) ?? Promise.resolve();
|
|
103
|
+
const running = previous.catch(() => undefined).then(operation);
|
|
104
|
+
const marker = running.then(() => undefined, () => undefined);
|
|
105
|
+
// eslint-disable-next-line functional/immutable-data -- per-project async queue
|
|
106
|
+
configWriteQueues.set(projectRoot, marker);
|
|
107
|
+
try {
|
|
108
|
+
return await running;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
if (configWriteQueues.get(projectRoot) === marker) {
|
|
112
|
+
// eslint-disable-next-line functional/immutable-data -- completed queue slot
|
|
113
|
+
configWriteQueues.delete(projectRoot);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Capture a bounded regular file without following a symlink or special entry.
|
|
119
|
+
* @param target - Fixed config path inside the project root
|
|
120
|
+
* @param filename - Public filename used only in safe diagnostics
|
|
121
|
+
* @param projectRoot - Anchored directory identity checked around file access
|
|
122
|
+
* @returns Existing strict-JSON image, or an empty absent-file image
|
|
123
|
+
*/
|
|
124
|
+
async function readConfigSnapshot(target, filename, projectRoot) {
|
|
125
|
+
try {
|
|
126
|
+
await assertProjectRootIdentity(projectRoot);
|
|
127
|
+
const before = await lstat(target);
|
|
128
|
+
if (!before.isFile()) {
|
|
129
|
+
throw new Error(`${filename} must be a regular file`);
|
|
130
|
+
}
|
|
131
|
+
if (before.size > MAX_CONFIG_BYTES) {
|
|
132
|
+
throw new Error(`${filename} exceeds the 128 KiB safety limit`);
|
|
133
|
+
}
|
|
134
|
+
const handle = await open(target, "r");
|
|
135
|
+
try {
|
|
136
|
+
const opened = await handle.stat();
|
|
137
|
+
if (!opened.isFile() ||
|
|
138
|
+
opened.dev !== before.dev ||
|
|
139
|
+
opened.ino !== before.ino) {
|
|
140
|
+
throw new Error(`${filename} changed while it was opened`);
|
|
141
|
+
}
|
|
142
|
+
const bytes = await readBounded(handle);
|
|
143
|
+
const after = await handle.stat();
|
|
144
|
+
if (after.dev !== opened.dev ||
|
|
145
|
+
after.ino !== opened.ino ||
|
|
146
|
+
after.size !== bytes.byteLength) {
|
|
147
|
+
throw new Error(`${filename} changed while it was read`);
|
|
148
|
+
}
|
|
149
|
+
const text = decodeUtf8(bytes, filename);
|
|
150
|
+
const parsed = parseConfigDocument(text, filename);
|
|
151
|
+
await assertProjectRootIdentity(projectRoot);
|
|
152
|
+
return {
|
|
153
|
+
target,
|
|
154
|
+
filename,
|
|
155
|
+
bytes,
|
|
156
|
+
text,
|
|
157
|
+
document: parsed,
|
|
158
|
+
mode: before.mode & 0o7777,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
await handle.close();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (error.code === "ENOENT") {
|
|
167
|
+
await assertProjectRootIdentity(projectRoot);
|
|
168
|
+
return {
|
|
169
|
+
target,
|
|
170
|
+
filename,
|
|
171
|
+
bytes: undefined,
|
|
172
|
+
text: "{}\n",
|
|
173
|
+
document: {},
|
|
174
|
+
mode: undefined,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Decode without replacement characters so a write can never normalize unsafe
|
|
182
|
+
* source bytes into a different valid document.
|
|
183
|
+
* @param bytes - Exact bounded file image
|
|
184
|
+
* @param filename - Safe fixed filename used in diagnostics
|
|
185
|
+
* @returns Valid UTF-8 text
|
|
186
|
+
*/
|
|
187
|
+
function decodeUtf8(bytes, filename) {
|
|
188
|
+
try {
|
|
189
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
throw new Error(`${filename} must contain valid UTF-8`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Read no more than the configured limit plus one sentinel byte.
|
|
197
|
+
* @param handle - Already inode-verified regular-file handle
|
|
198
|
+
* @param position - Next byte offset, used by bounded recursive reads
|
|
199
|
+
* @param chunks - Immutable chunks collected so far
|
|
200
|
+
* @returns Exact file bytes when within the safety limit
|
|
201
|
+
*/
|
|
202
|
+
async function readBounded(handle, position = 0, chunks = []) {
|
|
203
|
+
if (position > MAX_CONFIG_BYTES) {
|
|
204
|
+
throw new Error("Config file exceeds the 128 KiB safety limit");
|
|
205
|
+
}
|
|
206
|
+
const remaining = MAX_CONFIG_BYTES + 1 - position;
|
|
207
|
+
const buffer = Buffer.alloc(Math.min(READ_CHUNK_BYTES, remaining));
|
|
208
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
209
|
+
if (bytesRead === 0) {
|
|
210
|
+
return Buffer.concat(chunks, position);
|
|
211
|
+
}
|
|
212
|
+
return await readBounded(handle, position + bytesRead, [
|
|
213
|
+
...chunks,
|
|
214
|
+
buffer.subarray(0, bytesRead),
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Apply ordered dot-path edits while preserving untouched source bytes.
|
|
219
|
+
* @param snapshot - Strict JSON source image
|
|
220
|
+
* @param removals - Owner paths that must not remain in this non-owner file
|
|
221
|
+
* @param changes - Routed values for this one target
|
|
222
|
+
* @param exactRemovals - Exact property paths whose segments may contain dots
|
|
223
|
+
* @returns Rendered text plus the matching prospective object
|
|
224
|
+
*/
|
|
225
|
+
function prepareConfig(snapshot, removals, changes, exactRemovals = []) {
|
|
226
|
+
return {
|
|
227
|
+
snapshot,
|
|
228
|
+
...renderConfigChanges(snapshot, removals, changes, exactRemovals),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Refuse replacement of an existing target whose owner intentionally removed
|
|
233
|
+
* every write bit; directory-level rename permission must not bypass that
|
|
234
|
+
* policy, and a mixed request must fail before its first publish.
|
|
235
|
+
* @param prepared - Fully rendered target awaiting publication
|
|
236
|
+
*/
|
|
237
|
+
function assertWritableWhenChanged(prepared) {
|
|
238
|
+
if (prepared.changed &&
|
|
239
|
+
prepared.snapshot.mode !== undefined &&
|
|
240
|
+
(prepared.snapshot.mode & WRITE_PERMISSION_BITS) === 0) {
|
|
241
|
+
throw new Error(`${prepared.snapshot.filename} is read-only`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Bound the rendered image, not only the source snapshot. Surgical insertion
|
|
246
|
+
* can grow a previously valid file, and both targets must be rejected before
|
|
247
|
+
* the first publish if either prospective image crosses the shared limit.
|
|
248
|
+
* @param prepared - Fully rendered target awaiting publication
|
|
249
|
+
*/
|
|
250
|
+
function assertPreparedSize(prepared) {
|
|
251
|
+
if (Buffer.byteLength(prepared.text, "utf8") > MAX_CONFIG_BYTES) {
|
|
252
|
+
throw new Error(`${prepared.snapshot.filename} exceeds the 128 KiB safety limit`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Atomically publish a changed target after proving its source image is fresh.
|
|
257
|
+
* A mode change is part of that image because restoring snapshot permissions
|
|
258
|
+
* would otherwise undo a concurrent hardening even when the bytes still match.
|
|
259
|
+
* @param prepared - Rendered target and its original snapshot
|
|
260
|
+
* @param local - Whether a new file must be restricted to owner-only access
|
|
261
|
+
* @param projectRoot - Anchored directory identity checked around publication
|
|
262
|
+
*/
|
|
263
|
+
async function publishPrepared(prepared, local, projectRoot) {
|
|
264
|
+
if (!prepared.changed)
|
|
265
|
+
return;
|
|
266
|
+
const mode = prepared.snapshot.mode ?? (local ? 0o600 : undefined);
|
|
267
|
+
await assertProjectRootIdentity(projectRoot);
|
|
268
|
+
await writeFileAtomically(prepared.snapshot.target, prepared.text, {
|
|
269
|
+
...(mode === undefined ? {} : { mode }),
|
|
270
|
+
beforeRename: async () => {
|
|
271
|
+
await assertProjectRootIdentity(projectRoot);
|
|
272
|
+
const current = await readConfigSnapshot(prepared.snapshot.target, prepared.snapshot.filename, projectRoot);
|
|
273
|
+
if (!sameBytes(current.bytes, prepared.snapshot.bytes) ||
|
|
274
|
+
current.mode !== prepared.snapshot.mode) {
|
|
275
|
+
throw new Error(`${prepared.snapshot.filename} changed before atomic replacement`);
|
|
276
|
+
}
|
|
277
|
+
await assertProjectRootIdentity(projectRoot);
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
await assertProjectRootIdentity(projectRoot);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Compare absent/present source images without coercing UTF-8 bytes.
|
|
284
|
+
* @param current - Bytes observed immediately before replacement
|
|
285
|
+
* @param original - Bytes used to render the prospective document
|
|
286
|
+
* @returns Whether the target still contains the exact source image
|
|
287
|
+
*/
|
|
288
|
+
function sameBytes(current, original) {
|
|
289
|
+
if (current === undefined || original === undefined) {
|
|
290
|
+
return current === original;
|
|
291
|
+
}
|
|
292
|
+
return current.equals(original);
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=ui-config-write-persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-config-write-persistence.js","sourceRoot":"","sources":["../../src/cli/ui-config-write-persistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ui-config-write-property-edits.ts
|
|
3
|
+
* @description Minimal strict-JSON property edits against original source offsets.
|
|
4
|
+
* @module cli/ui-config-write-property-edits
|
|
5
|
+
*/
|
|
6
|
+
import { type Edit as JsonTextEdit, type Node as JsonNode } from "jsonc-parser";
|
|
7
|
+
import type { JsonValue } from "../sync/json-path.js";
|
|
8
|
+
/** One minimal source-relative property mutation after overlap collapse. */
|
|
9
|
+
export interface ConfigPatch {
|
|
10
|
+
/** Original syntax-tree path to replace, remove, or insert. */
|
|
11
|
+
readonly path: readonly string[];
|
|
12
|
+
/** Final value, or undefined when the property must be absent. */
|
|
13
|
+
readonly value: JsonValue | undefined;
|
|
14
|
+
/** Stable request order used for grouped insertions. */
|
|
15
|
+
readonly order: number;
|
|
16
|
+
}
|
|
17
|
+
/** Direct child mutations sharing one original object node. */
|
|
18
|
+
export interface ObjectPatchGroup {
|
|
19
|
+
/** Original path of the object containing the direct children. */
|
|
20
|
+
readonly parentPath: readonly string[];
|
|
21
|
+
/** Direct property names to remove. */
|
|
22
|
+
readonly removals: readonly string[];
|
|
23
|
+
/** Missing direct properties to insert in request order. */
|
|
24
|
+
readonly insertions: readonly ConfigPatch[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Plan direct child changes inside one original object.
|
|
28
|
+
* @param text - Original strict JSON text
|
|
29
|
+
* @param tree - Original syntax tree
|
|
30
|
+
* @param group - Direct removals and insertions for one object
|
|
31
|
+
* @returns Minimal edits for this object, all against original offsets
|
|
32
|
+
*/
|
|
33
|
+
export declare function planObjectPatchEdits(text: string, tree: JsonNode, group: ObjectPatchGroup): readonly JsonTextEdit[];
|
|
34
|
+
/**
|
|
35
|
+
* Serialize a JSON value while refusing the impossible `undefined` result.
|
|
36
|
+
* @param value - Registry-validated JSON value
|
|
37
|
+
* @returns Strict JSON syntax for only the targeted value span
|
|
38
|
+
*/
|
|
39
|
+
export declare function serializeConfigValue(value: JsonValue): string;
|
|
40
|
+
//# sourceMappingURL=ui-config-write-property-edits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-config-write-property-edits.d.ts","sourceRoot":"","sources":["../../src/cli/ui-config-write-property-edits.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,IAAI,IAAI,YAAY,EACzB,KAAK,IAAI,IAAI,QAAQ,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IACtC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,EAAE,SAAS,WAAW,EAAE,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,gBAAgB,GACtB,SAAS,YAAY,EAAE,CA+BzB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAM7D"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ui-config-write-property-edits.ts
|
|
3
|
+
* @description Minimal strict-JSON property edits against original source offsets.
|
|
4
|
+
* @module cli/ui-config-write-property-edits
|
|
5
|
+
*/
|
|
6
|
+
import { findNodeAtLocation, } from "jsonc-parser";
|
|
7
|
+
/**
|
|
8
|
+
* Plan direct child changes inside one original object.
|
|
9
|
+
* @param text - Original strict JSON text
|
|
10
|
+
* @param tree - Original syntax tree
|
|
11
|
+
* @param group - Direct removals and insertions for one object
|
|
12
|
+
* @returns Minimal edits for this object, all against original offsets
|
|
13
|
+
*/
|
|
14
|
+
export function planObjectPatchEdits(text, tree, group) {
|
|
15
|
+
const objectNode = findObjectNode(tree, group.parentPath);
|
|
16
|
+
const properties = objectNode.children ?? [];
|
|
17
|
+
const kept = properties.filter(property => !group.removals.includes(propertyKey(property)));
|
|
18
|
+
if (properties.length > 0 &&
|
|
19
|
+
kept.length === 0 &&
|
|
20
|
+
group.insertions.length > 0) {
|
|
21
|
+
const first = requireNode(properties[0]);
|
|
22
|
+
const last = requireNode(properties.at(-1));
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
offset: first.offset,
|
|
26
|
+
length: nodeEnd(last) - first.offset,
|
|
27
|
+
content: renderInsertedProperties(text, objectNode, group.insertions),
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
return [
|
|
32
|
+
...planPropertyRemovals(text, properties, group.removals),
|
|
33
|
+
...planPropertyInsertions(text, objectNode, properties, kept, group.insertions),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Serialize a JSON value while refusing the impossible `undefined` result.
|
|
38
|
+
* @param value - Registry-validated JSON value
|
|
39
|
+
* @returns Strict JSON syntax for only the targeted value span
|
|
40
|
+
*/
|
|
41
|
+
export function serializeConfigValue(value) {
|
|
42
|
+
const serialized = JSON.stringify(value);
|
|
43
|
+
if (serialized === undefined) {
|
|
44
|
+
throw new Error("Config value is not JSON-serializable");
|
|
45
|
+
}
|
|
46
|
+
return serialized;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve and require an original object node at a property path.
|
|
50
|
+
* @param tree - Original strict-JSON syntax tree
|
|
51
|
+
* @param path - Object property path
|
|
52
|
+
* @returns Original object node at that path
|
|
53
|
+
*/
|
|
54
|
+
function findObjectNode(tree, path) {
|
|
55
|
+
const node = path.length === 0 ? tree : findNodeAtLocation(tree, [...path]);
|
|
56
|
+
if (node?.type !== "object") {
|
|
57
|
+
throw new Error("Config surgical edit parent is not an object");
|
|
58
|
+
}
|
|
59
|
+
return node;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Require a syntax node that an earlier length check proved exists.
|
|
63
|
+
* @param node - Candidate property node
|
|
64
|
+
* @returns Present node or a deterministic planning failure
|
|
65
|
+
*/
|
|
66
|
+
function requireNode(node) {
|
|
67
|
+
if (node === undefined) {
|
|
68
|
+
throw new Error("Config surgical edit lost its source property");
|
|
69
|
+
}
|
|
70
|
+
return node;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read a property node's decoded key.
|
|
74
|
+
* @param property - Original direct property node
|
|
75
|
+
* @returns Decoded strict-JSON property name
|
|
76
|
+
*/
|
|
77
|
+
function propertyKey(property) {
|
|
78
|
+
const key = property.children?.[0]?.value;
|
|
79
|
+
if (property.type !== "property" || typeof key !== "string") {
|
|
80
|
+
throw new Error("Config surgical edit encountered an invalid property");
|
|
81
|
+
}
|
|
82
|
+
return key;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compute a node's exclusive original-text end offset.
|
|
86
|
+
* @param node - Original syntax-tree node
|
|
87
|
+
* @returns First source offset after the node
|
|
88
|
+
*/
|
|
89
|
+
function nodeEnd(node) {
|
|
90
|
+
return node.offset + node.length;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Delete consecutive target properties with one delimiter-aware edit per run.
|
|
94
|
+
* @param text - Original strict JSON text
|
|
95
|
+
* @param properties - Direct properties in original source order
|
|
96
|
+
* @param removalKeys - Direct keys absent from the prospective object
|
|
97
|
+
* @returns Non-overlapping deletions that retain every kept property byte
|
|
98
|
+
*/
|
|
99
|
+
function planPropertyRemovals(text, properties, removalKeys) {
|
|
100
|
+
const indices = properties
|
|
101
|
+
.map((property, index) => removalKeys.includes(propertyKey(property)) ? index : -1)
|
|
102
|
+
.filter(index => index >= 0);
|
|
103
|
+
const runs = indices.reduce((result, index) => {
|
|
104
|
+
const previous = result.at(-1);
|
|
105
|
+
return previous !== undefined && previous[1] + 1 === index
|
|
106
|
+
? [...result.slice(0, -1), [previous[0], index]]
|
|
107
|
+
: [...result, [index, index]];
|
|
108
|
+
}, []);
|
|
109
|
+
return runs.map(([firstIndex, lastIndex]) => {
|
|
110
|
+
const first = requireNode(properties[firstIndex]);
|
|
111
|
+
const last = requireNode(properties[lastIndex]);
|
|
112
|
+
const next = properties[lastIndex + 1];
|
|
113
|
+
if (next !== undefined) {
|
|
114
|
+
return {
|
|
115
|
+
offset: first.offset,
|
|
116
|
+
length: next.offset - first.offset,
|
|
117
|
+
content: "",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const previous = properties[firstIndex - 1];
|
|
121
|
+
if (previous === undefined) {
|
|
122
|
+
return {
|
|
123
|
+
offset: first.offset,
|
|
124
|
+
length: nodeEnd(last) - first.offset,
|
|
125
|
+
content: "",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const comma = requireComma(text, nodeEnd(previous), first.offset);
|
|
129
|
+
return { offset: comma, length: nodeEnd(last) - comma, content: "" };
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Locate the strict-JSON comma separating adjacent property nodes.
|
|
134
|
+
* @param text - Original strict JSON text
|
|
135
|
+
* @param start - End offset of the preceding property
|
|
136
|
+
* @param end - Start offset of the following property
|
|
137
|
+
* @returns Comma offset between those properties
|
|
138
|
+
*/
|
|
139
|
+
function requireComma(text, start, end) {
|
|
140
|
+
const comma = text.indexOf(",", start);
|
|
141
|
+
if (comma === -1 || comma >= end) {
|
|
142
|
+
throw new Error("Config surgical edit could not locate a property comma");
|
|
143
|
+
}
|
|
144
|
+
return comma;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Insert missing properties beside a retained anchor or inside an empty object.
|
|
148
|
+
* @param text - Original strict JSON text
|
|
149
|
+
* @param objectNode - Original parent object
|
|
150
|
+
* @param properties - All original direct properties
|
|
151
|
+
* @param kept - Direct properties not removed by this transaction
|
|
152
|
+
* @param insertions - Missing properties with final prospective values
|
|
153
|
+
* @returns Zero or one source-relative insertion
|
|
154
|
+
*/
|
|
155
|
+
function planPropertyInsertions(text, objectNode, properties, kept, insertions) {
|
|
156
|
+
if (insertions.length === 0)
|
|
157
|
+
return [];
|
|
158
|
+
const content = renderInsertedProperties(text, objectNode, insertions);
|
|
159
|
+
const anchor = kept.at(-1);
|
|
160
|
+
if (anchor !== undefined) {
|
|
161
|
+
return [
|
|
162
|
+
{
|
|
163
|
+
offset: nodeEnd(anchor),
|
|
164
|
+
length: 0,
|
|
165
|
+
content: `${inferPropertySeparator(text, objectNode, properties)}${content}`,
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
const interior = text.slice(objectNode.offset + 1, nodeEnd(objectNode) - 1);
|
|
170
|
+
const prefix = interior.includes("\n")
|
|
171
|
+
? `${inferEol(text)}${inferChildIndent(text, objectNode)}`
|
|
172
|
+
: "";
|
|
173
|
+
return [
|
|
174
|
+
{
|
|
175
|
+
offset: objectNode.offset + 1,
|
|
176
|
+
length: 0,
|
|
177
|
+
content: `${prefix}${content}`,
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Render only new property syntax; existing property text never enters here.
|
|
183
|
+
* @param text - Original strict JSON text
|
|
184
|
+
* @param objectNode - Parent object supplying separator style
|
|
185
|
+
* @param insertions - Missing properties in stable request order
|
|
186
|
+
* @returns Strict JSON property fragments
|
|
187
|
+
*/
|
|
188
|
+
function renderInsertedProperties(text, objectNode, insertions) {
|
|
189
|
+
const separator = inferPropertySeparator(text, objectNode, objectNode.children ?? []);
|
|
190
|
+
return insertions
|
|
191
|
+
.map(patch => {
|
|
192
|
+
const key = patch.path.at(-1);
|
|
193
|
+
if (key === undefined || patch.value === undefined) {
|
|
194
|
+
throw new Error("Config insertion is missing a property value");
|
|
195
|
+
}
|
|
196
|
+
return `${JSON.stringify(key)}: ${serializeConfigValue(patch.value)}`;
|
|
197
|
+
})
|
|
198
|
+
.join(separator);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Reuse an original property separator, falling back to inferred indentation.
|
|
202
|
+
* @param text - Original strict JSON text
|
|
203
|
+
* @param objectNode - Parent object supplying the fallback style
|
|
204
|
+
* @param properties - Original direct properties
|
|
205
|
+
* @returns Comma plus whitespace before the next property
|
|
206
|
+
*/
|
|
207
|
+
function inferPropertySeparator(text, objectNode, properties) {
|
|
208
|
+
const [first, second] = properties;
|
|
209
|
+
if (first !== undefined && second !== undefined) {
|
|
210
|
+
const comma = requireComma(text, nodeEnd(first), second.offset);
|
|
211
|
+
return text.slice(comma, second.offset);
|
|
212
|
+
}
|
|
213
|
+
if (first === undefined)
|
|
214
|
+
return ",";
|
|
215
|
+
const prefix = text.slice(objectNode.offset + 1, first.offset);
|
|
216
|
+
if (prefix.includes("\n")) {
|
|
217
|
+
return `,${inferEol(text)}${lineIndentAt(text, first.offset)}`;
|
|
218
|
+
}
|
|
219
|
+
return prefix.length > 0 ? `,${prefix}` : ",";
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Infer the original newline sequence without normalizing source bytes.
|
|
223
|
+
* @param text - Original strict JSON text
|
|
224
|
+
* @returns CRLF when present, otherwise LF
|
|
225
|
+
*/
|
|
226
|
+
function inferEol(text) {
|
|
227
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Read exact indentation immediately before a node on its source line.
|
|
231
|
+
* @param text - Original strict JSON text
|
|
232
|
+
* @param offset - Node start offset
|
|
233
|
+
* @returns Whitespace-only line prefix, or empty for inline nodes
|
|
234
|
+
*/
|
|
235
|
+
function lineIndentAt(text, offset) {
|
|
236
|
+
const lineStart = text.lastIndexOf("\n", offset - 1) + 1;
|
|
237
|
+
const prefix = text.slice(lineStart, offset);
|
|
238
|
+
return /^[ \t]*$/u.test(prefix) ? prefix : "";
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Infer one child indentation level for a genuinely empty multiline object.
|
|
242
|
+
* @param text - Original strict JSON text
|
|
243
|
+
* @param objectNode - Empty object receiving its first property
|
|
244
|
+
* @returns Parent indentation plus the document's indentation unit
|
|
245
|
+
*/
|
|
246
|
+
function inferChildIndent(text, objectNode) {
|
|
247
|
+
const indentation = /\r?\n([ \t]+)"/u.exec(text)?.[1] ?? " ";
|
|
248
|
+
return `${lineIndentAt(text, objectNode.offset)}${indentation}`;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=ui-config-write-property-edits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-config-write-property-edits.js","sourceRoot":"","sources":["../../src/cli/ui-config-write-property-edits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance cleanup routing for human-authored UI config writes.
|
|
3
|
+
* @module cli/ui-config-write-provenance
|
|
4
|
+
*/
|
|
5
|
+
import type { JsonValue } from "../sync/json-path.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve descendant human edits to literal registry-owner provenance paths.
|
|
8
|
+
* @param changes - Committed registry-root or descendant edits
|
|
9
|
+
* @returns Unique exact paths under `_lisaSync.populated`
|
|
10
|
+
*/
|
|
11
|
+
export declare function provenanceRemovals(changes: Readonly<Record<string, JsonValue>>): readonly (readonly string[])[];
|
|
12
|
+
//# sourceMappingURL=ui-config-write-provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-config-write-provenance.d.ts","sourceRoot":"","sources":["../../src/cli/ui-config-write-provenance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAItD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,GAC3C,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAKhC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SYNC_REGISTRY } from "../sync/registry.js";
|
|
2
|
+
import { populationPropertyPath } from "../sync/sync-population.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve descendant human edits to literal registry-owner provenance paths.
|
|
5
|
+
* @param changes - Committed registry-root or descendant edits
|
|
6
|
+
* @returns Unique exact paths under `_lisaSync.populated`
|
|
7
|
+
*/
|
|
8
|
+
export function provenanceRemovals(changes) {
|
|
9
|
+
const owners = Object.keys(changes)
|
|
10
|
+
.map(syncOwnerKey)
|
|
11
|
+
.filter((owner) => owner !== undefined);
|
|
12
|
+
return [...new Set(owners)].map(populationPropertyPath);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Find the most specific registry owner for one already-routed key.
|
|
16
|
+
* @param key - Registry root or descendant dot path
|
|
17
|
+
* @returns Owning registry key, when present
|
|
18
|
+
*/
|
|
19
|
+
function syncOwnerKey(key) {
|
|
20
|
+
return SYNC_REGISTRY.reduce((owner, entry) => {
|
|
21
|
+
if (key !== entry.key && !key.startsWith(`${entry.key}.`)) {
|
|
22
|
+
return owner;
|
|
23
|
+
}
|
|
24
|
+
return owner === undefined || entry.key.length > owner.length
|
|
25
|
+
? entry.key
|
|
26
|
+
: owner;
|
|
27
|
+
}, undefined);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=ui-config-write-provenance.js.map
|