@codyswann/lisa 4.17.18 → 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.
Files changed (98) hide show
  1. package/dist/cli/ui-config-write-document.d.ts +53 -0
  2. package/dist/cli/ui-config-write-document.d.ts.map +1 -0
  3. package/dist/cli/ui-config-write-document.js +212 -0
  4. package/dist/cli/ui-config-write-document.js.map +1 -0
  5. package/dist/cli/ui-config-write-patch-plan.d.ts +30 -0
  6. package/dist/cli/ui-config-write-patch-plan.d.ts.map +1 -0
  7. package/dist/cli/ui-config-write-patch-plan.js +151 -0
  8. package/dist/cli/ui-config-write-patch-plan.js.map +1 -0
  9. package/dist/cli/ui-config-write-persistence.d.ts +19 -0
  10. package/dist/cli/ui-config-write-persistence.d.ts.map +1 -0
  11. package/dist/cli/ui-config-write-persistence.js +294 -0
  12. package/dist/cli/ui-config-write-persistence.js.map +1 -0
  13. package/dist/cli/ui-config-write-property-edits.d.ts +40 -0
  14. package/dist/cli/ui-config-write-property-edits.d.ts.map +1 -0
  15. package/dist/cli/ui-config-write-property-edits.js +250 -0
  16. package/dist/cli/ui-config-write-property-edits.js.map +1 -0
  17. package/dist/cli/ui-config-write-provenance.d.ts +12 -0
  18. package/dist/cli/ui-config-write-provenance.d.ts.map +1 -0
  19. package/dist/cli/ui-config-write-provenance.js +29 -0
  20. package/dist/cli/ui-config-write-provenance.js.map +1 -0
  21. package/dist/cli/ui-config-write-root-identity.d.ts +25 -0
  22. package/dist/cli/ui-config-write-root-identity.d.ts.map +1 -0
  23. package/dist/cli/ui-config-write-root-identity.js +66 -0
  24. package/dist/cli/ui-config-write-root-identity.js.map +1 -0
  25. package/dist/cli/ui-config-write.d.ts.map +1 -1
  26. package/dist/cli/ui-config-write.js +47 -92
  27. package/dist/cli/ui-config-write.js.map +1 -1
  28. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  29. package/dist/core/lisa-owned-hash-ledger.js +4 -0
  30. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  31. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  32. package/dist/core/upstream-evidence-manifest.js +8 -2
  33. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  34. package/dist/sync/json-path.d.ts +10 -0
  35. package/dist/sync/json-path.d.ts.map +1 -1
  36. package/dist/sync/json-path.js +14 -0
  37. package/dist/sync/json-path.js.map +1 -1
  38. package/dist/sync/sync-population.d.ts +6 -0
  39. package/dist/sync/sync-population.d.ts.map +1 -1
  40. package/dist/sync/sync-population.js +8 -0
  41. package/dist/sync/sync-population.js.map +1 -1
  42. package/dist/utils/atomic-file-write.d.ts +5 -4
  43. package/dist/utils/atomic-file-write.d.ts.map +1 -1
  44. package/dist/utils/atomic-file-write.js +3 -0
  45. package/dist/utils/atomic-file-write.js.map +1 -1
  46. package/package.json +2 -2
  47. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-agy/plugin.json +1 -1
  50. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  53. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-expo-agy/plugin.json +1 -1
  60. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  65. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  70. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  75. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  79. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  80. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-rails-agy/plugin.json +1 -1
  85. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  89. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  90. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  95. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  97. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +10 -3
  98. 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