@codyswann/lisa 2.199.0 → 2.200.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/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +30 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/sync-cmd.d.ts +21 -0
- package/dist/cli/sync-cmd.d.ts.map +1 -0
- package/dist/cli/sync-cmd.js +43 -0
- package/dist/cli/sync-cmd.js.map +1 -0
- package/dist/cli/ui-cmd.d.ts +35 -0
- package/dist/cli/ui-cmd.d.ts.map +1 -0
- package/dist/cli/ui-cmd.js +104 -0
- package/dist/cli/ui-cmd.js.map +1 -0
- package/dist/sync/config-sync.d.ts +42 -0
- package/dist/sync/config-sync.d.ts.map +1 -0
- package/dist/sync/config-sync.js +272 -0
- package/dist/sync/config-sync.js.map +1 -0
- package/dist/sync/json-path.d.ts +58 -0
- package/dist/sync/json-path.d.ts.map +1 -0
- package/dist/sync/json-path.js +107 -0
- package/dist/sync/json-path.js.map +1 -0
- package/dist/sync/registry.d.ts +64 -0
- package/dist/sync/registry.d.ts.map +1 -0
- package/dist/sync/registry.js +207 -0
- package/dist/sync/registry.js.map +1 -0
- package/package.json +2 -1
- 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 +180 -0
- package/ui/index.html +5396 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config-sync engine: makes `.lisa.config.json` (plus the per-developer
|
|
3
|
+
* `.lisa.config.local.json` overlay) the source of truth for every registry
|
|
4
|
+
* setting.
|
|
5
|
+
*
|
|
6
|
+
* Populate direction (only when a config value is completely missing):
|
|
7
|
+
* absorb the value from an existing artifact file if one holds it, otherwise
|
|
8
|
+
* fall back to the built-in default. Pure-default populations are recorded in
|
|
9
|
+
* the config's `_lisaSync.populated` block so a later Lisa version that ships
|
|
10
|
+
* a changed default can distinguish "still the default" (safe to update) from
|
|
11
|
+
* "the project chose this value" (never touched).
|
|
12
|
+
*
|
|
13
|
+
* Sync direction (always): the effective config value is written into every
|
|
14
|
+
* artifact file that exists on disk — config wins.
|
|
15
|
+
* @module sync/config-sync
|
|
16
|
+
*/
|
|
17
|
+
import * as fse from "fs-extra";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { deepMerge, readJsonOrNull, writeJson } from "../utils/index.js";
|
|
20
|
+
import { fillMissing, getAtPath, isJsonObject, jsonEquals, setAtPath, } from "./json-path.js";
|
|
21
|
+
import { REQUIRED_KEYS, SYNC_REGISTRY, } from "./registry.js";
|
|
22
|
+
/** Key holding sync provenance metadata inside `.lisa.config.json`. */
|
|
23
|
+
export const SYNC_METADATA_KEY = "_lisaSync";
|
|
24
|
+
/**
|
|
25
|
+
* Check whether a registry relevance condition matches the merged config.
|
|
26
|
+
* @param merged - Merged (committed + local) config view
|
|
27
|
+
* @param condition - `"section"` (path exists) or `"path=value"` (equality)
|
|
28
|
+
* @returns True when the condition matches
|
|
29
|
+
*/
|
|
30
|
+
function conditionMatches(merged, condition) {
|
|
31
|
+
const eqIndex = condition.indexOf("=");
|
|
32
|
+
if (eqIndex === -1) {
|
|
33
|
+
return getAtPath(merged, condition) !== undefined;
|
|
34
|
+
}
|
|
35
|
+
const value = getAtPath(merged, condition.slice(0, eqIndex));
|
|
36
|
+
return value === condition.slice(eqIndex + 1);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether a registry entry applies to this project's current config.
|
|
40
|
+
* @param merged - Merged (committed + local) config view
|
|
41
|
+
* @param relevantWhen - Entry conditions (absent = always relevant)
|
|
42
|
+
* @returns True when the entry should be synced
|
|
43
|
+
*/
|
|
44
|
+
function isRelevant(merged, relevantWhen) {
|
|
45
|
+
if (relevantWhen === undefined) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return relevantWhen.some(condition => conditionMatches(merged, condition));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the first existing artifact's value for a setting.
|
|
52
|
+
* @param destDir - Project root
|
|
53
|
+
* @param entry - Registry entry
|
|
54
|
+
* @returns The artifact value, or undefined when no artifact holds one
|
|
55
|
+
*/
|
|
56
|
+
async function readArtifactValue(destDir, entry) {
|
|
57
|
+
const bindings = entry.artifacts ?? [];
|
|
58
|
+
const reads = await Promise.all(bindings.map(async (binding) => {
|
|
59
|
+
const parsed = await readJsonOrNull(path.join(destDir, binding.file));
|
|
60
|
+
return parsed === null ? undefined : getAtPath(parsed, binding.pointer);
|
|
61
|
+
}));
|
|
62
|
+
return reads.find(value => value !== undefined);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the recorded populated-default value for a key, if any. Provenance
|
|
66
|
+
* keys are stored literally (dots included) inside `_lisaSync.populated`.
|
|
67
|
+
* @param committed - Committed config object
|
|
68
|
+
* @param key - Config key
|
|
69
|
+
* @returns The value recorded at population time, or undefined
|
|
70
|
+
*/
|
|
71
|
+
function recordedPopulation(committed, key) {
|
|
72
|
+
const populated = getAtPath(committed, `${SYNC_METADATA_KEY}.populated`);
|
|
73
|
+
return isJsonObject(populated) ? populated[key] : undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Record that a key was auto-populated with a default value, storing the key
|
|
77
|
+
* literally (dots included) so later runs can detect default evolution.
|
|
78
|
+
* @param committed - Committed config object
|
|
79
|
+
* @param key - Config key
|
|
80
|
+
* @param value - The default value written
|
|
81
|
+
* @returns Updated committed config
|
|
82
|
+
*/
|
|
83
|
+
function recordPopulation(committed, key, value) {
|
|
84
|
+
const populated = getAtPath(committed, `${SYNC_METADATA_KEY}.populated`);
|
|
85
|
+
const populatedObject = isJsonObject(populated) ? populated : {};
|
|
86
|
+
return setAtPath(committed, `${SYNC_METADATA_KEY}.populated`, {
|
|
87
|
+
...populatedObject,
|
|
88
|
+
[key]: value,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the next state with an updated committed config and appended action.
|
|
93
|
+
* @param state - Current sync state
|
|
94
|
+
* @param committed - Updated committed config
|
|
95
|
+
* @param action - Action to append to the report
|
|
96
|
+
* @returns Updated state
|
|
97
|
+
*/
|
|
98
|
+
function withAction(state, committed, action) {
|
|
99
|
+
return { ...state, committed, actions: [...state.actions, action] };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Populate a config key that is completely missing: absorb the artifact
|
|
103
|
+
* value when one exists, otherwise write the built-in default and record it.
|
|
104
|
+
* @param state - Current sync state
|
|
105
|
+
* @param entry - Registry entry
|
|
106
|
+
* @param artifactValue - Value found in an existing artifact, if any
|
|
107
|
+
* @returns Updated state
|
|
108
|
+
*/
|
|
109
|
+
function populateMissing(state, entry, artifactValue) {
|
|
110
|
+
if (artifactValue !== undefined) {
|
|
111
|
+
const absorbed = fillMissing(artifactValue, entry.defaultValue);
|
|
112
|
+
return withAction(state, setAtPath(state.committed, entry.key, absorbed), {
|
|
113
|
+
key: entry.key,
|
|
114
|
+
kind: "absorbed-artifact",
|
|
115
|
+
detail: `value absorbed from ${entry.artifacts?.[0]?.file ?? "artifact"}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const committed = recordPopulation(setAtPath(state.committed, entry.key, entry.defaultValue), entry.key, entry.defaultValue);
|
|
119
|
+
return withAction(state, committed, {
|
|
120
|
+
key: entry.key,
|
|
121
|
+
kind: "populated-default",
|
|
122
|
+
detail: "built-in default written to .lisa.config.json",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the next committed config for one entry (populate direction).
|
|
127
|
+
* @param state - Current sync state
|
|
128
|
+
* @param entry - Registry entry
|
|
129
|
+
* @param merged - Merged config view (local overlay applied)
|
|
130
|
+
* @param artifactValue - Value found in an existing artifact, if any
|
|
131
|
+
* @returns Updated state
|
|
132
|
+
*/
|
|
133
|
+
function populateEntry(state, entry, merged, artifactValue) {
|
|
134
|
+
const configValue = getAtPath(merged, entry.key);
|
|
135
|
+
if (configValue === undefined) {
|
|
136
|
+
return populateMissing(state, entry, artifactValue);
|
|
137
|
+
}
|
|
138
|
+
const recorded = recordedPopulation(state.committed, entry.key);
|
|
139
|
+
if (recorded !== undefined &&
|
|
140
|
+
jsonEquals(configValue, recorded) &&
|
|
141
|
+
!jsonEquals(configValue, entry.defaultValue)) {
|
|
142
|
+
const committed = recordPopulation(setAtPath(state.committed, entry.key, entry.defaultValue), entry.key, entry.defaultValue);
|
|
143
|
+
return withAction(state, committed, {
|
|
144
|
+
key: entry.key,
|
|
145
|
+
kind: "default-evolved",
|
|
146
|
+
detail: "value still matched the old default; updated to the new one",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const filled = fillMissing(configValue, entry.defaultValue);
|
|
150
|
+
if (!jsonEquals(filled, configValue)) {
|
|
151
|
+
return withAction(state, setAtPath(state.committed, entry.key, filled), {
|
|
152
|
+
key: entry.key,
|
|
153
|
+
kind: "filled-missing",
|
|
154
|
+
detail: "missing sub-keys filled with defaults",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return state;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Queue artifact writes for one entry (sync direction: config wins). Only
|
|
161
|
+
* files that already exist on disk are written — sync never scaffolds
|
|
162
|
+
* artifacts into stacks that do not use them.
|
|
163
|
+
* @param state - Current sync state
|
|
164
|
+
* @param entry - Registry entry
|
|
165
|
+
* @param destDir - Project root
|
|
166
|
+
* @param local - Local config overlay
|
|
167
|
+
* @returns Updated state
|
|
168
|
+
*/
|
|
169
|
+
async function syncArtifacts(state, entry, destDir, local) {
|
|
170
|
+
const bindings = entry.artifacts ?? [];
|
|
171
|
+
if (bindings.length === 0) {
|
|
172
|
+
return state;
|
|
173
|
+
}
|
|
174
|
+
const effective = getAtPath(deepMerge(state.committed, local), entry.key);
|
|
175
|
+
if (effective === undefined) {
|
|
176
|
+
return state;
|
|
177
|
+
}
|
|
178
|
+
return bindings.reduce(async (statePromise, binding) => {
|
|
179
|
+
const current = await statePromise;
|
|
180
|
+
const filePath = path.join(destDir, binding.file);
|
|
181
|
+
const pending = current.artifactWrites.get(binding.file);
|
|
182
|
+
const parsed = pending ?? (await readJsonOrNull(filePath)) ?? undefined;
|
|
183
|
+
if (parsed === undefined && !(await fse.pathExists(filePath))) {
|
|
184
|
+
return current;
|
|
185
|
+
}
|
|
186
|
+
const fileObject = isJsonObject(parsed) ? parsed : {};
|
|
187
|
+
if (jsonEquals(getAtPath(fileObject, binding.pointer), effective)) {
|
|
188
|
+
return current;
|
|
189
|
+
}
|
|
190
|
+
const updated = setAtPath(fileObject, binding.pointer, effective);
|
|
191
|
+
const writes = new Map([
|
|
192
|
+
...current.artifactWrites,
|
|
193
|
+
[binding.file, updated],
|
|
194
|
+
]);
|
|
195
|
+
return {
|
|
196
|
+
...current,
|
|
197
|
+
artifactWrites: writes,
|
|
198
|
+
actions: [
|
|
199
|
+
...current.actions,
|
|
200
|
+
{
|
|
201
|
+
key: entry.key,
|
|
202
|
+
kind: "artifact-synced",
|
|
203
|
+
detail: `${binding.file} updated from config`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}, Promise.resolve(state));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Report required keys that are missing from the merged config.
|
|
211
|
+
* @param merged - Merged config view
|
|
212
|
+
* @returns Missing required keys with their setup hints
|
|
213
|
+
*/
|
|
214
|
+
function findMissingRequired(merged) {
|
|
215
|
+
return REQUIRED_KEYS.filter(required => isRelevant(merged, required.relevantWhen) &&
|
|
216
|
+
getAtPath(merged, required.key) === undefined).map(required => ({ key: required.key, setupHint: required.setupHint }));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Run one populate-and-sync pass over a project.
|
|
220
|
+
*
|
|
221
|
+
* Precedence per setting: existing config value → existing artifact value →
|
|
222
|
+
* built-in default. After population, every existing artifact file is
|
|
223
|
+
* rewritten from the (local-overlaid) config value.
|
|
224
|
+
* @param destDir - Absolute path to the project root
|
|
225
|
+
* @param options - Sync options
|
|
226
|
+
* @returns Report of every action taken (or planned, when dryRun)
|
|
227
|
+
*/
|
|
228
|
+
export async function runConfigSync(destDir, options = {}) {
|
|
229
|
+
const committedPath = path.join(destDir, ".lisa.config.json");
|
|
230
|
+
const localPath = path.join(destDir, ".lisa.config.local.json");
|
|
231
|
+
const committedRaw = await readJsonOrNull(committedPath);
|
|
232
|
+
const localRaw = await readJsonOrNull(localPath);
|
|
233
|
+
const committed = isJsonObject(committedRaw) ? committedRaw : {};
|
|
234
|
+
const local = isJsonObject(localRaw) ? localRaw : {};
|
|
235
|
+
const initial = {
|
|
236
|
+
committed,
|
|
237
|
+
actions: [],
|
|
238
|
+
artifactWrites: new Map(),
|
|
239
|
+
};
|
|
240
|
+
const finalState = await SYNC_REGISTRY.reduce(async (statePromise, entry) => {
|
|
241
|
+
const state = await statePromise;
|
|
242
|
+
const merged = deepMerge(state.committed, local);
|
|
243
|
+
if (!isRelevant(merged, entry.relevantWhen)) {
|
|
244
|
+
return state;
|
|
245
|
+
}
|
|
246
|
+
const artifactValue = await readArtifactValue(destDir, entry);
|
|
247
|
+
const populated = populateEntry(state, entry, merged, artifactValue);
|
|
248
|
+
return syncArtifacts(populated, entry, destDir, local);
|
|
249
|
+
}, Promise.resolve(initial));
|
|
250
|
+
const mergedFinal = deepMerge(finalState.committed, local);
|
|
251
|
+
if (options.dryRun !== true) {
|
|
252
|
+
await persistState(destDir, committed, finalState);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
actions: finalState.actions,
|
|
256
|
+
missingRequired: findMissingRequired(mergedFinal),
|
|
257
|
+
dryRun: options.dryRun === true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Write the updated committed config and every queued artifact file.
|
|
262
|
+
* @param destDir - Project root
|
|
263
|
+
* @param originalCommitted - Committed config as read at the start of the run
|
|
264
|
+
* @param state - Final sync state
|
|
265
|
+
*/
|
|
266
|
+
async function persistState(destDir, originalCommitted, state) {
|
|
267
|
+
if (!jsonEquals(originalCommitted, state.committed)) {
|
|
268
|
+
await writeJson(path.join(destDir, ".lisa.config.json"), state.committed);
|
|
269
|
+
}
|
|
270
|
+
await Promise.all(Array.from(state.artifactWrites.entries()).map(([file, content]) => writeJson(path.join(destDir, file), content)));
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=config-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-sync.js","sourceRoot":"","sources":["../../src/sync/config-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dot-path helpers for reading and immutably updating nested JSON values.
|
|
3
|
+
*
|
|
4
|
+
* Used by the config-sync engine to address settings inside
|
|
5
|
+
* `.lisa.config.json` (e.g. `quality.testCoverage.global.statements`) and
|
|
6
|
+
* inside mirrored artifact files (e.g. the `thresholds` key of
|
|
7
|
+
* `stryker.conf.json`).
|
|
8
|
+
* @module sync/json-path
|
|
9
|
+
*/
|
|
10
|
+
/** Any JSON-compatible value. */
|
|
11
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
12
|
+
readonly [key: string]: JsonValue;
|
|
13
|
+
};
|
|
14
|
+
/** A JSON object record (non-array, non-null). */
|
|
15
|
+
export type JsonObject = {
|
|
16
|
+
readonly [key: string]: JsonValue;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Check whether a value is a plain (non-array) JSON object.
|
|
20
|
+
* @param value - Candidate value
|
|
21
|
+
* @returns True when the value is a plain JSON object record
|
|
22
|
+
*/
|
|
23
|
+
export declare function isJsonObject(value: unknown): value is JsonObject;
|
|
24
|
+
/**
|
|
25
|
+
* Read the value at a dot path inside a JSON object.
|
|
26
|
+
* @param root - Object to read from
|
|
27
|
+
* @param dotPath - Dot-separated key path (empty string returns the root)
|
|
28
|
+
* @returns The value at the path, or undefined when any segment is missing
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAtPath(root: unknown, dotPath: string): JsonValue | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Return a copy of a JSON object with the value at a dot path replaced.
|
|
33
|
+
* Missing intermediate objects are created; existing siblings are preserved.
|
|
34
|
+
* The input object is never mutated.
|
|
35
|
+
* @param root - Object to copy
|
|
36
|
+
* @param dotPath - Dot-separated key path (empty string replaces the root)
|
|
37
|
+
* @param value - Value to write at the path
|
|
38
|
+
* @returns A new object with the update applied
|
|
39
|
+
*/
|
|
40
|
+
export declare function setAtPath(root: JsonObject, dotPath: string, value: JsonValue): JsonObject;
|
|
41
|
+
/**
|
|
42
|
+
* Compare two JSON-compatible values for structural equality, independent of
|
|
43
|
+
* object key insertion order.
|
|
44
|
+
* @param a - First value
|
|
45
|
+
* @param b - Second value
|
|
46
|
+
* @returns True when the values are structurally equal
|
|
47
|
+
*/
|
|
48
|
+
export declare function jsonEquals(a: unknown, b: unknown): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Fill missing keys of a JSON value from a fallback, recursively. Present
|
|
51
|
+
* values always win over the fallback; only absent object keys are filled.
|
|
52
|
+
* Arrays and scalars are taken wholesale from whichever side is present.
|
|
53
|
+
* @param value - The preferred value (may be undefined)
|
|
54
|
+
* @param fallback - Values used only where `value` has gaps
|
|
55
|
+
* @returns The value with gaps filled from the fallback
|
|
56
|
+
*/
|
|
57
|
+
export declare function fillMissing(value: JsonValue | undefined, fallback: JsonValue): JsonValue;
|
|
58
|
+
//# sourceMappingURL=json-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-path.d.ts","sourceRoot":"","sources":["../../src/sync/json-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iCAAiC;AACjC,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE1C,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,SAAS,CAYvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,GACf,UAAU,CAkBZ;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAe1D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,QAAQ,EAAE,SAAS,GAClB,SAAS,CAoBX"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dot-path helpers for reading and immutably updating nested JSON values.
|
|
3
|
+
*
|
|
4
|
+
* Used by the config-sync engine to address settings inside
|
|
5
|
+
* `.lisa.config.json` (e.g. `quality.testCoverage.global.statements`) and
|
|
6
|
+
* inside mirrored artifact files (e.g. the `thresholds` key of
|
|
7
|
+
* `stryker.conf.json`).
|
|
8
|
+
* @module sync/json-path
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Check whether a value is a plain (non-array) JSON object.
|
|
12
|
+
* @param value - Candidate value
|
|
13
|
+
* @returns True when the value is a plain JSON object record
|
|
14
|
+
*/
|
|
15
|
+
export function isJsonObject(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read the value at a dot path inside a JSON object.
|
|
20
|
+
* @param root - Object to read from
|
|
21
|
+
* @param dotPath - Dot-separated key path (empty string returns the root)
|
|
22
|
+
* @returns The value at the path, or undefined when any segment is missing
|
|
23
|
+
*/
|
|
24
|
+
export function getAtPath(root, dotPath) {
|
|
25
|
+
if (dotPath === "") {
|
|
26
|
+
return root;
|
|
27
|
+
}
|
|
28
|
+
const segments = dotPath.split(".");
|
|
29
|
+
return segments.reduce((node, segment) => isJsonObject(node)
|
|
30
|
+
? node[segment]
|
|
31
|
+
: undefined, root);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Return a copy of a JSON object with the value at a dot path replaced.
|
|
35
|
+
* Missing intermediate objects are created; existing siblings are preserved.
|
|
36
|
+
* The input object is never mutated.
|
|
37
|
+
* @param root - Object to copy
|
|
38
|
+
* @param dotPath - Dot-separated key path (empty string replaces the root)
|
|
39
|
+
* @param value - Value to write at the path
|
|
40
|
+
* @returns A new object with the update applied
|
|
41
|
+
*/
|
|
42
|
+
export function setAtPath(root, dotPath, value) {
|
|
43
|
+
if (dotPath === "") {
|
|
44
|
+
if (!isJsonObject(value)) {
|
|
45
|
+
throw new Error("setAtPath: replacing the document root requires an object value");
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
const dotIndex = dotPath.indexOf(".");
|
|
50
|
+
const head = dotIndex === -1 ? dotPath : dotPath.slice(0, dotIndex);
|
|
51
|
+
const rest = dotIndex === -1 ? "" : dotPath.slice(dotIndex + 1);
|
|
52
|
+
if (rest === "") {
|
|
53
|
+
return { ...root, [head]: value };
|
|
54
|
+
}
|
|
55
|
+
const child = root[head];
|
|
56
|
+
const childObject = isJsonObject(child) ? child : {};
|
|
57
|
+
return { ...root, [head]: setAtPath(childObject, rest, value) };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compare two JSON-compatible values for structural equality, independent of
|
|
61
|
+
* object key insertion order.
|
|
62
|
+
* @param a - First value
|
|
63
|
+
* @param b - Second value
|
|
64
|
+
* @returns True when the values are structurally equal
|
|
65
|
+
*/
|
|
66
|
+
export function jsonEquals(a, b) {
|
|
67
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
68
|
+
return (a.length === b.length && a.every((entry, i) => jsonEquals(entry, b[i])));
|
|
69
|
+
}
|
|
70
|
+
if (isJsonObject(a) && isJsonObject(b)) {
|
|
71
|
+
const aKeys = Object.keys(a);
|
|
72
|
+
const bKeys = Object.keys(b);
|
|
73
|
+
return (aKeys.length === bKeys.length &&
|
|
74
|
+
aKeys.every(key => Object.hasOwn(b, key) && jsonEquals(a[key], b[key])));
|
|
75
|
+
}
|
|
76
|
+
return a === b;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Fill missing keys of a JSON value from a fallback, recursively. Present
|
|
80
|
+
* values always win over the fallback; only absent object keys are filled.
|
|
81
|
+
* Arrays and scalars are taken wholesale from whichever side is present.
|
|
82
|
+
* @param value - The preferred value (may be undefined)
|
|
83
|
+
* @param fallback - Values used only where `value` has gaps
|
|
84
|
+
* @returns The value with gaps filled from the fallback
|
|
85
|
+
*/
|
|
86
|
+
export function fillMissing(value, fallback) {
|
|
87
|
+
if (value === undefined) {
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
90
|
+
if (isJsonObject(value) && isJsonObject(fallback)) {
|
|
91
|
+
const keys = new Set([...Object.keys(fallback), ...Object.keys(value)]);
|
|
92
|
+
const entries = Array.from(keys).map((key) => {
|
|
93
|
+
const own = Object.hasOwn(value, key) ? value[key] : undefined;
|
|
94
|
+
const fill = Object.hasOwn(fallback, key) ? fallback[key] : undefined;
|
|
95
|
+
if (own === undefined) {
|
|
96
|
+
return [key, fill];
|
|
97
|
+
}
|
|
98
|
+
if (fill === undefined) {
|
|
99
|
+
return [key, own];
|
|
100
|
+
}
|
|
101
|
+
return [key, fillMissing(own, fill)];
|
|
102
|
+
});
|
|
103
|
+
return Object.fromEntries(entries);
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=json-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-path.js","sourceRoot":"","sources":["../../src/sync/json-path.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of synced settings: the single map between `.lisa.config.json`
|
|
3
|
+
* keys, their built-in defaults, and the project artifact files that mirror
|
|
4
|
+
* them (test thresholds, lint budgets, mutation gates).
|
|
5
|
+
*
|
|
6
|
+
* The registry drives `lisa sync`: every entry is populated into the config
|
|
7
|
+
* when missing (absorbing an existing artifact value first, falling back to
|
|
8
|
+
* the built-in default), and afterwards the config value is written back into
|
|
9
|
+
* each artifact file — config is the source of truth unless a value is
|
|
10
|
+
* completely missing.
|
|
11
|
+
* @module sync/registry
|
|
12
|
+
*/
|
|
13
|
+
import type { JsonValue } from "./json-path.js";
|
|
14
|
+
/**
|
|
15
|
+
* A project file that mirrors a synced setting. Artifact files are only ever
|
|
16
|
+
* written when they already exist on disk — sync never scaffolds an artifact
|
|
17
|
+
* into a project whose stack does not use it.
|
|
18
|
+
*/
|
|
19
|
+
export interface ArtifactBinding {
|
|
20
|
+
/** Repo-relative path of the mirrored file */
|
|
21
|
+
readonly file: string;
|
|
22
|
+
/** Dot path of the mirrored value inside the file ("" = whole document) */
|
|
23
|
+
readonly pointer: string;
|
|
24
|
+
}
|
|
25
|
+
/** One synced setting: a config key, its default, and optional mirrors. */
|
|
26
|
+
export interface SyncedSetting {
|
|
27
|
+
/** Dot path of the setting inside `.lisa.config.json` */
|
|
28
|
+
readonly key: string;
|
|
29
|
+
/** Built-in default used when neither config nor artifact has a value */
|
|
30
|
+
readonly defaultValue: JsonValue;
|
|
31
|
+
/** Files that mirror this value (written from config on every sync) */
|
|
32
|
+
readonly artifacts?: readonly ArtifactBinding[];
|
|
33
|
+
/**
|
|
34
|
+
* Entry applies when ANY condition matches (absent = always applies).
|
|
35
|
+
* `"github"` means the `github` section exists; `"tracker=jira"` means the
|
|
36
|
+
* `tracker` key equals `"jira"`.
|
|
37
|
+
*/
|
|
38
|
+
readonly relevantWhen?: readonly string[];
|
|
39
|
+
/** One-line human description used in sync reports */
|
|
40
|
+
readonly description: string;
|
|
41
|
+
}
|
|
42
|
+
/** A key sync cannot default and must report when missing. */
|
|
43
|
+
export interface RequiredKey {
|
|
44
|
+
/** Dot path of the required setting */
|
|
45
|
+
readonly key: string;
|
|
46
|
+
/** Applies when ANY condition matches (absent = always required) */
|
|
47
|
+
readonly relevantWhen?: readonly string[];
|
|
48
|
+
/** Setup command that provisions the key */
|
|
49
|
+
readonly setupHint: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* All settings `lisa sync` manages. Local-only keys (`atlassian.email`,
|
|
53
|
+
* `intake.assignee`, `jira.verified_workflow_hash`, secret paths) are
|
|
54
|
+
* deliberately absent — sync never populates the committed file with values
|
|
55
|
+
* that belong in `.lisa.config.local.json`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const SYNC_REGISTRY: readonly SyncedSetting[];
|
|
58
|
+
/**
|
|
59
|
+
* Keys sync can never default: they identify external systems and must come
|
|
60
|
+
* from a human (via the matching `/lisa:setup:*` skill). Sync reports them
|
|
61
|
+
* when missing instead of inventing values.
|
|
62
|
+
*/
|
|
63
|
+
export declare const REQUIRED_KEYS: readonly RequiredKey[];
|
|
64
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sync/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;IACjC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAoCD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,aAAa,EAyHjD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EA0C/C,CAAC"}
|