@monoes/monodesign 1.2.20 → 1.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/cli/engine/design-system-checks.mjs +381 -0
- package/cli/engine/design-system-parse.mjs +433 -0
- package/cli/engine/design-system.mjs +20 -736
- package/cli/engine/engines/regex/detect-text-matchers.mjs +371 -0
- package/cli/engine/engines/regex/detect-text-run.mjs +186 -0
- package/cli/engine/engines/regex/detect-text.mjs +11 -523
- package/cli/engine/engines/static-html/css-cascade-color.mjs +219 -0
- package/cli/engine/engines/static-html/css-cascade-static-data.mjs +138 -0
- package/cli/engine/engines/static-html/css-cascade-static-dom.mjs +132 -0
- package/cli/engine/engines/static-html/css-cascade-static-parse.mjs +393 -0
- package/cli/engine/engines/static-html/css-cascade.mjs +24 -837
- package/cli/lib/monodesign-config-consent.mjs +93 -0
- package/cli/lib/monodesign-config-core.mjs +37 -0
- package/cli/lib/monodesign-config-detection.mjs +133 -0
- package/cli/lib/monodesign-config-filter.mjs +198 -0
- package/cli/lib/monodesign-config-ignore-value.mjs +212 -0
- package/cli/lib/monodesign-config.mjs +21 -623
- package/package.json +2 -2
- package/skill/scripts/context-fields.mjs +65 -0
- package/skill/scripts/context-resolve.mjs +425 -0
- package/skill/scripts/context-update.mjs +151 -0
- package/skill/scripts/context-utils.mjs +68 -0
- package/skill/scripts/context-workspace.mjs +245 -0
- package/skill/scripts/context.mjs +17 -891
- package/skill/scripts/hook-admin-config.mjs +152 -0
- package/skill/scripts/hook-admin-ignore.mjs +131 -0
- package/skill/scripts/hook-admin-manifest.mjs +292 -0
- package/skill/scripts/hook-admin.mjs +8 -541
- package/skill/scripts/hook-lib-cache.mjs +145 -0
- package/skill/scripts/hook-lib-config.mjs +292 -0
- package/skill/scripts/hook-lib-detector.mjs +72 -0
- package/skill/scripts/hook-lib-events.mjs +182 -0
- package/skill/scripts/hook-lib-findings.mjs +201 -0
- package/skill/scripts/hook-lib-ignore.mjs +205 -0
- package/skill/scripts/hook-lib-render.mjs +273 -0
- package/skill/scripts/hook-lib-run.mjs +281 -0
- package/skill/scripts/hook-lib-scan-targets.mjs +128 -0
- package/skill/scripts/hook-lib.mjs +71 -1747
- package/skill/scripts/lib/design-parser-colors.mjs +182 -0
- package/skill/scripts/lib/design-parser-extract.mjs +333 -0
- package/skill/scripts/lib/design-parser-sections.mjs +214 -0
- package/skill/scripts/lib/design-parser-yaml.mjs +135 -0
- package/skill/scripts/lib/design-parser.mjs +10 -783
- package/skill/scripts/live/manual-apply-chunk.mjs +151 -0
- package/skill/scripts/live/manual-apply-compact.mjs +122 -0
- package/skill/scripts/live/manual-apply-evidence.mjs +115 -0
- package/skill/scripts/live/manual-apply-log.mjs +179 -0
- package/skill/scripts/live/manual-apply-transaction.mjs +122 -0
- package/skill/scripts/live/manual-apply.mjs +65 -640
- package/skill/scripts/live/svelte-component-accept.mjs +248 -0
- package/skill/scripts/live/svelte-component-css.mjs +230 -0
- package/skill/scripts/live/svelte-component-markup.mjs +104 -0
- package/skill/scripts/live/svelte-component-scaffold.mjs +118 -0
- package/skill/scripts/live/svelte-component-session.mjs +158 -0
- package/skill/scripts/live/svelte-component.mjs +32 -792
- package/skill/scripts/live-accept-mutate.mjs +152 -0
- package/skill/scripts/live-accept-parse.mjs +370 -0
- package/skill/scripts/live-accept-scrub.mjs +59 -0
- package/skill/scripts/live-accept-search.mjs +58 -0
- package/skill/scripts/live-accept.mjs +15 -631
- package/skill/scripts/live-commit-manual-edits-paths.mjs +27 -0
- package/skill/scripts/live-commit-manual-edits-repair.mjs +166 -0
- package/skill/scripts/live-commit-manual-edits-rollback.mjs +231 -0
- package/skill/scripts/live-commit-manual-edits-utils.mjs +167 -0
- package/skill/scripts/live-commit-manual-edits-verify.mjs +356 -0
- package/skill/scripts/live-commit-manual-edits.mjs +24 -881
- package/skill/scripts/live-copy-edit-agent.mjs +16 -551
- package/skill/scripts/live-copy-edit-compact.mjs +137 -0
- package/skill/scripts/live-copy-edit-post-apply.mjs +175 -0
- package/skill/scripts/live-copy-edit-runners.mjs +297 -0
- package/skill/scripts/live-inject-csp.mjs +132 -0
- package/skill/scripts/live-inject-files.mjs +229 -0
- package/skill/scripts/live-inject-tag.mjs +114 -0
- package/skill/scripts/live-inject.mjs +13 -440
- package/skill/scripts/live-server-poll.mjs +232 -0
- package/skill/scripts/live-server-routes.mjs +449 -0
- package/skill/scripts/live-server-state.mjs +361 -0
- package/skill/scripts/live-server.mjs +10 -946
- package/skill/scripts/live-wrap-css.mjs +132 -0
- package/skill/scripts/live-wrap-manual-edit.mjs +153 -0
- package/skill/scripts/live-wrap-search.mjs +247 -0
- package/skill/scripts/live-wrap.mjs +21 -484
- package/skill/scripts/palette-seeds.mjs +401 -0
- package/skill/scripts/palette.mjs +1 -395
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-verification helpers for live-commit-manual-edits.mjs: checking
|
|
3
|
+
* whether a staged copy-edit op's newText actually landed in a plausible
|
|
4
|
+
* source location, and rolling that up per entry.
|
|
5
|
+
*
|
|
6
|
+
* Split out of live-commit-manual-edits.mjs. See that file for context.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { candidatesForEntry, uniqueStrings } from './live-commit-manual-edits-utils.mjs';
|
|
10
|
+
import { normalizeRelativeFile } from './live-commit-manual-edits-paths.mjs';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
function sourceHintWindowFailure(cwd, op) {
|
|
15
|
+
const hint = op?.sourceHint;
|
|
16
|
+
if (!hint?.file || !hint.line) return null;
|
|
17
|
+
const relative = normalizeRelativeFile(cwd, hint.file);
|
|
18
|
+
if (!relative) return null;
|
|
19
|
+
const absolute = path.resolve(cwd, relative);
|
|
20
|
+
let content;
|
|
21
|
+
try { content = fs.readFileSync(absolute, 'utf-8'); } catch { return null; }
|
|
22
|
+
const lines = content.split('\n');
|
|
23
|
+
const line = Math.max(1, Number(hint.line) || 1);
|
|
24
|
+
const lineText = lines[line - 1] || '';
|
|
25
|
+
const start = Math.max(0, line - 5);
|
|
26
|
+
const end = Math.min(lines.length, line + 4);
|
|
27
|
+
if (
|
|
28
|
+
typeof op.originalText === 'string'
|
|
29
|
+
&& op.originalText
|
|
30
|
+
&& lineText.includes(op.originalText)
|
|
31
|
+
&& !lineShowsAppliedOp(lineText, op)
|
|
32
|
+
) {
|
|
33
|
+
return {
|
|
34
|
+
file: relative,
|
|
35
|
+
line,
|
|
36
|
+
reason: 'source_hint_still_contains_original_text',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (lines.slice(start, end).some((candidateLine) => lineShowsAppliedOp(candidateLine, op))) return null;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function verificationTargetsForOp(batch, op, reportedFiles, cwd) {
|
|
44
|
+
const candidate = (batch.candidates || []).find((item) => item.entryId === op.entryId && item.ref === op.ref);
|
|
45
|
+
const out = [];
|
|
46
|
+
const reportedFileSet = new Set(reportedFiles || []);
|
|
47
|
+
const add = (file, line, kind) => {
|
|
48
|
+
const relativeFile = normalizeRelativeFile(cwd, file);
|
|
49
|
+
const lineNumber = Number(line);
|
|
50
|
+
if (!relativeFile || !Number.isFinite(lineNumber) || lineNumber < 1) return;
|
|
51
|
+
out.push({ file: relativeFile, line: lineNumber, kind, reported: reportedFileSet.has(relativeFile) });
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
add(op.sourceHint?.file, op.sourceHint?.line, 'source_hint');
|
|
55
|
+
add(candidate?.sourceHint?.relativeFile || candidate?.sourceHint?.file, candidate?.sourceHint?.line, 'candidate_source_hint');
|
|
56
|
+
for (const item of candidate?.textMatches || []) add(item.file, item.line, 'text_match');
|
|
57
|
+
for (const item of candidate?.objectKeyMatches || []) add(item.file, item.line, 'object_key_match');
|
|
58
|
+
for (const item of candidate?.locatorMatches || []) add(item.file, item.line, 'locator_match');
|
|
59
|
+
for (const item of candidate?.contextTextMatches || []) add(item.file, item.line, 'context_text_match');
|
|
60
|
+
|
|
61
|
+
// Manual copy edits often stage coupled leaves from the same UI object, e.g.
|
|
62
|
+
// a card label plus its count. Dynamic source stores both on the label/key
|
|
63
|
+
// line, so the count op may need the sibling label's data candidates.
|
|
64
|
+
for (const siblingCandidate of siblingCandidatesForEntry(batch, op)) {
|
|
65
|
+
add(siblingCandidate.sourceHint?.relativeFile || siblingCandidate.sourceHint?.file, siblingCandidate.sourceHint?.line, 'entry_source_hint');
|
|
66
|
+
for (const item of siblingCandidate.textMatches || []) add(item.file, item.line, 'entry_text_match');
|
|
67
|
+
for (const item of siblingCandidate.objectKeyMatches || []) add(item.file, item.line, 'entry_object_key_match');
|
|
68
|
+
for (const item of siblingCandidate.contextTextMatches || []) add(item.file, item.line, 'entry_context_text_match');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const relativeFile of reportedFiles || []) {
|
|
72
|
+
for (const target of locatorTargetsInFile(cwd, relativeFile, op)) {
|
|
73
|
+
out.push(target);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
return out.filter((target) => {
|
|
79
|
+
const key = `${target.file}:${target.line}:${target.kind}`;
|
|
80
|
+
if (seen.has(key)) return false;
|
|
81
|
+
seen.add(key);
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function objectKeyCandidatesForOp(batch, op) {
|
|
87
|
+
const candidates = (batch.candidates || [])
|
|
88
|
+
.filter((item) => item.entryId === op.entryId && item.ref === op.ref);
|
|
89
|
+
return candidates.flatMap((candidate) => candidate.objectKeyMatches || []);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function lineHasObjectKey(line, text) {
|
|
93
|
+
if (typeof text !== 'string' || text.length === 0) return false;
|
|
94
|
+
const quotedKey = new RegExp(`(^|[\\s,{])(['"\`])${escapeRegExp(text)}\\2\\s*:`);
|
|
95
|
+
if (quotedKey.test(line)) return true;
|
|
96
|
+
const identifierSafe = /^[A-Za-z_$][\w$]*$/.test(text);
|
|
97
|
+
if (!identifierSafe) return false;
|
|
98
|
+
const bareKey = new RegExp(`(^|[\\s,{])${escapeRegExp(text)}\\s*:`);
|
|
99
|
+
return bareKey.test(line);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function objectKeyMatchStillUsesOriginal(cwd, match, op) {
|
|
103
|
+
const relative = normalizeRelativeFile(cwd, match?.file);
|
|
104
|
+
const lineNumber = Number(match?.line);
|
|
105
|
+
if (!relative || !Number.isFinite(lineNumber) || lineNumber < 1) return false;
|
|
106
|
+
let lines;
|
|
107
|
+
try { lines = fs.readFileSync(path.resolve(cwd, relative), 'utf-8').split('\n'); } catch { return false; }
|
|
108
|
+
const start = Math.max(0, lineNumber - 4);
|
|
109
|
+
const end = Math.min(lines.length, lineNumber + 3);
|
|
110
|
+
const windowLines = lines.slice(start, end);
|
|
111
|
+
if (windowLines.some((line) => lineHasObjectKey(line, op.newText))) return false;
|
|
112
|
+
return windowLines.some((line) => lineHasObjectKey(line, op.originalText));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function coupledObjectKeyFailuresForOp(batch, op, cwd) {
|
|
116
|
+
if (
|
|
117
|
+
typeof op?.originalText !== 'string'
|
|
118
|
+
|| typeof op?.newText !== 'string'
|
|
119
|
+
|| op.originalText === op.newText
|
|
120
|
+
) return [];
|
|
121
|
+
return objectKeyCandidatesForOp(batch, op)
|
|
122
|
+
.filter((match) => objectKeyMatchStillUsesOriginal(cwd, match, op))
|
|
123
|
+
.map((match) => ({
|
|
124
|
+
ref: op.ref,
|
|
125
|
+
reason: 'source_verification_failed',
|
|
126
|
+
detail: 'edited_text_source_key_dependency_not_updated',
|
|
127
|
+
candidates: [{
|
|
128
|
+
file: normalizeRelativeFile(cwd, match.file) || match.file,
|
|
129
|
+
line: match.line,
|
|
130
|
+
kind: 'object_key_match',
|
|
131
|
+
reason: 'edited text is also a source key; update the coupled key to newText or fail the entry',
|
|
132
|
+
}],
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function siblingCandidatesForEntry(batch, op) {
|
|
137
|
+
if (!op?.entryId) return [];
|
|
138
|
+
return (batch.candidates || []).filter((item) => item.entryId === op.entryId && item.ref !== op.ref);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function locatorTargetsInFile(cwd, relativeFile, op) {
|
|
142
|
+
if (!opHasLocator(op)) return [];
|
|
143
|
+
const absolute = path.resolve(cwd, relativeFile);
|
|
144
|
+
let lines;
|
|
145
|
+
try { lines = fs.readFileSync(absolute, 'utf-8').split('\n'); } catch { return []; }
|
|
146
|
+
const out = [];
|
|
147
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
148
|
+
if (!lineMatchesManualEditLocator(lines[index], op)) continue;
|
|
149
|
+
out.push({ file: relativeFile, line: index + 1, kind: 'reported_locator_match' });
|
|
150
|
+
if (out.length >= 20) break;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function verificationTargetPasses(cwd, target, op) {
|
|
156
|
+
let lines;
|
|
157
|
+
try { lines = fs.readFileSync(path.resolve(cwd, target.file), 'utf-8').split('\n'); } catch { return false; }
|
|
158
|
+
return verificationTargetPassesLines(lines, target, op);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function verificationTargetPassesLines(lines, target, op) {
|
|
162
|
+
const line = lines[target.line - 1] || '';
|
|
163
|
+
if (lineShowsAppliedOp(line, op)) return true;
|
|
164
|
+
const originalText = typeof op?.originalText === 'string' ? op.originalText : '';
|
|
165
|
+
if (originalText && line.includes(originalText)) return false;
|
|
166
|
+
const kind = String(target.kind || '');
|
|
167
|
+
const canSearchWindow = target.reported
|
|
168
|
+
|| kind.includes('context_text_match')
|
|
169
|
+
|| kind.includes('object_key_match')
|
|
170
|
+
|| kind.includes('text_match');
|
|
171
|
+
if (!canSearchWindow) return false;
|
|
172
|
+
const radius = kind.includes('context_text_match') ? 20 : 4;
|
|
173
|
+
const start = Math.max(0, target.line - radius - 1);
|
|
174
|
+
const end = Math.min(lines.length, target.line + radius);
|
|
175
|
+
const windowLines = lines.slice(start, end);
|
|
176
|
+
if (windowLines.some((candidateLine) => lineShowsAppliedOp(candidateLine, op))) return true;
|
|
177
|
+
if (windowShowsAppliedOp(windowLines, op)) return true;
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function windowShowsAppliedOp(lines, op) {
|
|
182
|
+
const newText = typeof op?.newText === 'string' ? op.newText : '';
|
|
183
|
+
if (!newText) return false;
|
|
184
|
+
const originalText = typeof op?.originalText === 'string' ? op.originalText : '';
|
|
185
|
+
const normalizedNew = normalizeVerificationText(newText);
|
|
186
|
+
const normalizedOriginal = normalizeVerificationText(originalText);
|
|
187
|
+
const normalizedWindow = normalizeVerificationText(lines.join('\n'));
|
|
188
|
+
if (!normalizedNew || !normalizedWindow.includes(normalizedNew)) return false;
|
|
189
|
+
if (normalizedOriginal && !normalizedNew.includes(normalizedOriginal) && normalizedWindow.includes(normalizedOriginal)) return false;
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizeVerificationText(text) {
|
|
194
|
+
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function lineShowsAppliedOp(line, op) {
|
|
198
|
+
const originalText = typeof op?.originalText === 'string' ? op.originalText : '';
|
|
199
|
+
const newText = typeof op?.newText === 'string' ? op.newText : '';
|
|
200
|
+
const deletion = op?.deleted === true || newText.length === 0;
|
|
201
|
+
if (deletion) return !!originalText && !line.includes(originalText);
|
|
202
|
+
if (!line.includes(newText)) return false;
|
|
203
|
+
if (originalText && !newText.includes(originalText) && line.includes(originalText)) return false;
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function opHasLocator(op) {
|
|
208
|
+
return !!(
|
|
209
|
+
op?.tag
|
|
210
|
+
|| op?.elementId
|
|
211
|
+
|| (Array.isArray(op?.classes) && op.classes.filter(Boolean).length > 0)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function lineMatchesManualEditLocator(line, op) {
|
|
216
|
+
if (op.tag) {
|
|
217
|
+
const tagRe = new RegExp(`<\\s*${escapeRegExp(op.tag)}(?=[\\s>/]|$)`, 'i');
|
|
218
|
+
if (!tagRe.test(line)) return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (op.elementId) {
|
|
222
|
+
const idRe = new RegExp(`\\bid\\s*=\\s*["']${escapeRegExp(op.elementId)}["']`);
|
|
223
|
+
if (!idRe.test(line)) return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const classes = Array.isArray(op.classes) ? op.classes.filter(Boolean) : [];
|
|
227
|
+
for (const className of classes) {
|
|
228
|
+
if (!line.includes(className)) return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function verifyAppliedEntry({ batch, entry, reportedFiles, cwd }) {
|
|
235
|
+
const failures = [];
|
|
236
|
+
for (const rawOp of entry.ops || []) {
|
|
237
|
+
const op = { ...rawOp, entryId: entry.id };
|
|
238
|
+
if (op.deleted === true && typeof op.newText !== 'string') op.newText = '';
|
|
239
|
+
if (typeof op.newText !== 'string') {
|
|
240
|
+
failures.push({
|
|
241
|
+
ref: op.ref,
|
|
242
|
+
reason: 'source_verification_failed',
|
|
243
|
+
detail: 'missing_newText',
|
|
244
|
+
candidates: candidatesForEntry(batch, entry.id).slice(0, 12),
|
|
245
|
+
});
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const targets = verificationTargetsForOp(batch, op, reportedFiles, cwd);
|
|
249
|
+
const coupledObjectKeyFailures = coupledObjectKeyFailuresForOp(batch, op, cwd);
|
|
250
|
+
if (
|
|
251
|
+
coupledObjectKeyFailures.length === 0
|
|
252
|
+
&& targets.some((target) => verificationTargetPasses(cwd, target, op))
|
|
253
|
+
) continue;
|
|
254
|
+
|
|
255
|
+
if (coupledObjectKeyFailures.length > 0) {
|
|
256
|
+
failures.push(...coupledObjectKeyFailures.map((failure) => ({
|
|
257
|
+
...failure,
|
|
258
|
+
candidates: [
|
|
259
|
+
...(failure.candidates || []),
|
|
260
|
+
...targets.map((target) => ({ file: target.file, line: target.line, kind: target.kind })),
|
|
261
|
+
...candidatesForEntry(batch, entry.id),
|
|
262
|
+
].slice(0, 12),
|
|
263
|
+
})));
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const hintedOldText = sourceHintWindowFailure(cwd, op);
|
|
268
|
+
if (hintedOldText) {
|
|
269
|
+
failures.push({
|
|
270
|
+
ref: op.ref,
|
|
271
|
+
reason: 'source_verification_failed',
|
|
272
|
+
detail: hintedOldText.reason,
|
|
273
|
+
candidates: [hintedOldText, ...targets.map((target) => ({ file: target.file, line: target.line, kind: target.kind })), ...candidatesForEntry(batch, entry.id)].slice(0, 12),
|
|
274
|
+
});
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
failures.push({
|
|
279
|
+
ref: op.ref,
|
|
280
|
+
reason: 'source_verification_failed',
|
|
281
|
+
detail: op.newText.length === 0 ? 'originalText_still_present_in_plausible_source_location' : 'newText_not_found_in_plausible_source_location',
|
|
282
|
+
candidates: targets.map((target) => ({ file: target.file, line: target.line, kind: target.kind })).concat(candidatesForEntry(batch, entry.id)).slice(0, 12),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return failures;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function snapshotTargetPasses(snapshot, target, op) {
|
|
289
|
+
const before = snapshot.get(target.file)?.content;
|
|
290
|
+
if (typeof before !== 'string') return false;
|
|
291
|
+
return verificationTargetPassesLines(before.split('\n'), target, op);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function findUnappliedEntrySourceChanges({ batch, entries, reportedFiles, cwd, rollbackSnapshot }) {
|
|
295
|
+
const failures = [];
|
|
296
|
+
for (const entry of entries || []) {
|
|
297
|
+
for (const rawOp of entry.ops || []) {
|
|
298
|
+
const op = { ...rawOp, entryId: entry.id };
|
|
299
|
+
if (typeof op.newText !== 'string' || op.newText.length === 0) continue;
|
|
300
|
+
const targets = verificationTargetsForOp(batch, op, reportedFiles, cwd);
|
|
301
|
+
const leakedTargets = targets.filter((target) =>
|
|
302
|
+
verificationTargetPasses(cwd, target, op)
|
|
303
|
+
&& !snapshotTargetPasses(rollbackSnapshot, target, op)
|
|
304
|
+
);
|
|
305
|
+
if (leakedTargets.length === 0) continue;
|
|
306
|
+
failures.push({
|
|
307
|
+
id: entry.id,
|
|
308
|
+
reason: 'failed_entry_source_changed',
|
|
309
|
+
ref: op.ref,
|
|
310
|
+
newText: op.newText,
|
|
311
|
+
candidates: leakedTargets
|
|
312
|
+
.map((target) => ({ file: target.file, line: target.line, kind: target.kind }))
|
|
313
|
+
.concat(candidatesForEntry(batch, entry.id))
|
|
314
|
+
.slice(0, 12),
|
|
315
|
+
});
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return failures;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function verificationFailuresForEntries(batch, entries, reason, extra = {}) {
|
|
323
|
+
return entries.map((entry) => ({
|
|
324
|
+
id: entry.id,
|
|
325
|
+
reason,
|
|
326
|
+
candidates: candidatesForEntry(batch, entry.id),
|
|
327
|
+
...extra,
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function verifyEntriesAfterRepair({ batch, appliedEntryIds, files, cwd }) {
|
|
332
|
+
const reportedFiles = uniqueStrings(files || [])
|
|
333
|
+
.map((file) => normalizeRelativeFile(cwd, file))
|
|
334
|
+
.filter(Boolean);
|
|
335
|
+
const entries = (batch.entries || []).filter((entry) => appliedEntryIds.includes(entry.id));
|
|
336
|
+
const verifiedIds = [];
|
|
337
|
+
const failed = [];
|
|
338
|
+
for (const entry of entries) {
|
|
339
|
+
const failures = verifyAppliedEntry({ batch, entry, reportedFiles, cwd });
|
|
340
|
+
if (failures.length === 0) {
|
|
341
|
+
verifiedIds.push(entry.id);
|
|
342
|
+
} else {
|
|
343
|
+
failed.push({
|
|
344
|
+
id: entry.id,
|
|
345
|
+
reason: 'source_verification_failed',
|
|
346
|
+
failures,
|
|
347
|
+
candidates: candidatesForEntry(batch, entry.id),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return { verifiedIds, failed, reportedFiles };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function escapeRegExp(value) {
|
|
355
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
356
|
+
}
|