@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.
Files changed (85) hide show
  1. package/README.md +2 -2
  2. package/cli/engine/design-system-checks.mjs +381 -0
  3. package/cli/engine/design-system-parse.mjs +433 -0
  4. package/cli/engine/design-system.mjs +20 -736
  5. package/cli/engine/engines/regex/detect-text-matchers.mjs +371 -0
  6. package/cli/engine/engines/regex/detect-text-run.mjs +186 -0
  7. package/cli/engine/engines/regex/detect-text.mjs +11 -523
  8. package/cli/engine/engines/static-html/css-cascade-color.mjs +219 -0
  9. package/cli/engine/engines/static-html/css-cascade-static-data.mjs +138 -0
  10. package/cli/engine/engines/static-html/css-cascade-static-dom.mjs +132 -0
  11. package/cli/engine/engines/static-html/css-cascade-static-parse.mjs +393 -0
  12. package/cli/engine/engines/static-html/css-cascade.mjs +24 -837
  13. package/cli/lib/monodesign-config-consent.mjs +93 -0
  14. package/cli/lib/monodesign-config-core.mjs +37 -0
  15. package/cli/lib/monodesign-config-detection.mjs +133 -0
  16. package/cli/lib/monodesign-config-filter.mjs +198 -0
  17. package/cli/lib/monodesign-config-ignore-value.mjs +212 -0
  18. package/cli/lib/monodesign-config.mjs +21 -623
  19. package/package.json +2 -2
  20. package/skill/scripts/context-fields.mjs +65 -0
  21. package/skill/scripts/context-resolve.mjs +425 -0
  22. package/skill/scripts/context-update.mjs +151 -0
  23. package/skill/scripts/context-utils.mjs +68 -0
  24. package/skill/scripts/context-workspace.mjs +245 -0
  25. package/skill/scripts/context.mjs +17 -891
  26. package/skill/scripts/hook-admin-config.mjs +152 -0
  27. package/skill/scripts/hook-admin-ignore.mjs +131 -0
  28. package/skill/scripts/hook-admin-manifest.mjs +292 -0
  29. package/skill/scripts/hook-admin.mjs +8 -541
  30. package/skill/scripts/hook-lib-cache.mjs +145 -0
  31. package/skill/scripts/hook-lib-config.mjs +292 -0
  32. package/skill/scripts/hook-lib-detector.mjs +72 -0
  33. package/skill/scripts/hook-lib-events.mjs +182 -0
  34. package/skill/scripts/hook-lib-findings.mjs +201 -0
  35. package/skill/scripts/hook-lib-ignore.mjs +205 -0
  36. package/skill/scripts/hook-lib-render.mjs +273 -0
  37. package/skill/scripts/hook-lib-run.mjs +281 -0
  38. package/skill/scripts/hook-lib-scan-targets.mjs +128 -0
  39. package/skill/scripts/hook-lib.mjs +71 -1747
  40. package/skill/scripts/lib/design-parser-colors.mjs +182 -0
  41. package/skill/scripts/lib/design-parser-extract.mjs +333 -0
  42. package/skill/scripts/lib/design-parser-sections.mjs +214 -0
  43. package/skill/scripts/lib/design-parser-yaml.mjs +135 -0
  44. package/skill/scripts/lib/design-parser.mjs +10 -783
  45. package/skill/scripts/live/manual-apply-chunk.mjs +151 -0
  46. package/skill/scripts/live/manual-apply-compact.mjs +122 -0
  47. package/skill/scripts/live/manual-apply-evidence.mjs +115 -0
  48. package/skill/scripts/live/manual-apply-log.mjs +179 -0
  49. package/skill/scripts/live/manual-apply-transaction.mjs +122 -0
  50. package/skill/scripts/live/manual-apply.mjs +65 -640
  51. package/skill/scripts/live/svelte-component-accept.mjs +248 -0
  52. package/skill/scripts/live/svelte-component-css.mjs +230 -0
  53. package/skill/scripts/live/svelte-component-markup.mjs +104 -0
  54. package/skill/scripts/live/svelte-component-scaffold.mjs +118 -0
  55. package/skill/scripts/live/svelte-component-session.mjs +158 -0
  56. package/skill/scripts/live/svelte-component.mjs +32 -792
  57. package/skill/scripts/live-accept-mutate.mjs +152 -0
  58. package/skill/scripts/live-accept-parse.mjs +370 -0
  59. package/skill/scripts/live-accept-scrub.mjs +59 -0
  60. package/skill/scripts/live-accept-search.mjs +58 -0
  61. package/skill/scripts/live-accept.mjs +15 -631
  62. package/skill/scripts/live-commit-manual-edits-paths.mjs +27 -0
  63. package/skill/scripts/live-commit-manual-edits-repair.mjs +166 -0
  64. package/skill/scripts/live-commit-manual-edits-rollback.mjs +231 -0
  65. package/skill/scripts/live-commit-manual-edits-utils.mjs +167 -0
  66. package/skill/scripts/live-commit-manual-edits-verify.mjs +356 -0
  67. package/skill/scripts/live-commit-manual-edits.mjs +24 -881
  68. package/skill/scripts/live-copy-edit-agent.mjs +16 -551
  69. package/skill/scripts/live-copy-edit-compact.mjs +137 -0
  70. package/skill/scripts/live-copy-edit-post-apply.mjs +175 -0
  71. package/skill/scripts/live-copy-edit-runners.mjs +297 -0
  72. package/skill/scripts/live-inject-csp.mjs +132 -0
  73. package/skill/scripts/live-inject-files.mjs +229 -0
  74. package/skill/scripts/live-inject-tag.mjs +114 -0
  75. package/skill/scripts/live-inject.mjs +13 -440
  76. package/skill/scripts/live-server-poll.mjs +232 -0
  77. package/skill/scripts/live-server-routes.mjs +449 -0
  78. package/skill/scripts/live-server-state.mjs +361 -0
  79. package/skill/scripts/live-server.mjs +10 -946
  80. package/skill/scripts/live-wrap-css.mjs +132 -0
  81. package/skill/scripts/live-wrap-manual-edit.mjs +153 -0
  82. package/skill/scripts/live-wrap-search.mjs +247 -0
  83. package/skill/scripts/live-wrap.mjs +21 -484
  84. package/skill/scripts/palette-seeds.mjs +401 -0
  85. package/skill/scripts/palette.mjs +1 -395
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Repair-attempt loop for live-commit-manual-edits.mjs: when a batched AI
3
+ * apply fails post-apply validation, re-invoke the copy-edit agent with the
4
+ * failure detail attached, up to a bounded number of attempts.
5
+ *
6
+ * Split out of live-commit-manual-edits.mjs. See that file for context.
7
+ */
8
+
9
+ import { countByPage } from './live/manual-edits-buffer.mjs';
10
+ import {
11
+ runCopyEditBatchAgent,
12
+ runCopyEditPostApplyChecks,
13
+ } from './live-copy-edit-agent.mjs';
14
+ import {
15
+ candidatesForEntry,
16
+ mergeFailedEntries,
17
+ mergeUniqueStrings,
18
+ normalizeFailedEntries,
19
+ repairAttemptLimit,
20
+ summarizeAppliedEntries,
21
+ summarizeRepairFailures,
22
+ buildRepairBatch,
23
+ } from './live-commit-manual-edits-utils.mjs';
24
+ import {
25
+ verifyEntriesAfterRepair,
26
+ verificationFailuresForEntries,
27
+ } from './live-commit-manual-edits-verify.mjs';
28
+ import { clearAppliedEntries } from './live-commit-manual-edits-rollback.mjs';
29
+
30
+ export async function repairPostApplyValidation({
31
+ batch,
32
+ cwd,
33
+ pageUrl,
34
+ count,
35
+ provider,
36
+ env,
37
+ timeoutMs,
38
+ applyBatchToSource,
39
+ chatAvailable,
40
+ transactionId,
41
+ appliedEntryIds,
42
+ files,
43
+ failed,
44
+ notes,
45
+ warnings,
46
+ postChecks,
47
+ repairReason = 'post_apply_validation_failed',
48
+ repairFailures = null,
49
+ }) {
50
+ const maxAttempts = repairAttemptLimit(env);
51
+ let currentFiles = mergeUniqueStrings(files || []);
52
+ let currentAppliedIds = mergeUniqueStrings(appliedEntryIds || []);
53
+ let currentFailed = Array.isArray(failed) ? failed : [];
54
+ let currentNotes = Array.isArray(notes) ? notes : [];
55
+ let currentWarnings = Array.isArray(warnings) ? warnings : [];
56
+ let currentFailures = Array.isArray(repairFailures) ? repairFailures : (postChecks?.failures || []);
57
+
58
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
59
+ const repair = {
60
+ attempt,
61
+ maxAttempts,
62
+ transactionId: transactionId || null,
63
+ reason: repairReason,
64
+ failures: summarizeRepairFailures(currentFailures),
65
+ files: currentFiles,
66
+ pageUrl,
67
+ };
68
+ let repairResult;
69
+ try {
70
+ repairResult = await runCopyEditBatchAgent(buildRepairBatch(batch, repair), {
71
+ cwd,
72
+ provider,
73
+ env,
74
+ timeoutMs,
75
+ applyBatchToSource,
76
+ chatAvailable,
77
+ });
78
+ } catch (err) {
79
+ currentFailures = [{
80
+ reason: 'repair_agent_failed',
81
+ message: err.message || String(err),
82
+ }];
83
+ continue;
84
+ }
85
+
86
+ currentFiles = mergeUniqueStrings(currentFiles, repairResult.files || []);
87
+ currentNotes = [...currentNotes, ...(repairResult.notes || [])];
88
+ currentWarnings = [...currentWarnings, ...(repairResult.warnings || [])];
89
+ currentAppliedIds = mergeUniqueStrings(currentAppliedIds, repairResult.appliedEntryIds || []);
90
+ currentFailed = mergeFailedEntries(
91
+ currentFailed,
92
+ normalizeFailedEntries(batch, repairResult, 'repair_failed'),
93
+ );
94
+
95
+ const verified = verifyEntriesAfterRepair({
96
+ batch,
97
+ appliedEntryIds: currentAppliedIds,
98
+ files: currentFiles,
99
+ cwd,
100
+ });
101
+ if (verified.failed.length > 0) {
102
+ currentFailures = verified.failed;
103
+ continue;
104
+ }
105
+
106
+ const repairedChecks = runCopyEditPostApplyChecks({ cwd, files: currentFiles });
107
+ currentWarnings = [...currentWarnings, ...(repairedChecks.warnings || [])];
108
+ if (!repairedChecks.ok) {
109
+ currentFailures = repairedChecks.failures || [];
110
+ continue;
111
+ }
112
+
113
+ const cleared = clearAppliedEntries(cwd, verified.verifiedIds);
114
+ const counts = countByPage(cwd);
115
+ const verifiedIdSet = new Set(verified.verifiedIds);
116
+ return {
117
+ applied: summarizeAppliedEntries(batch.entries, verified.verifiedIds),
118
+ failed: mergeFailedEntries(currentFailed).filter((item) => !verifiedIdSet.has(item.id)),
119
+ files: currentFiles,
120
+ cleared,
121
+ count,
122
+ pageUrl,
123
+ warnings: currentWarnings,
124
+ notes: currentNotes,
125
+ repair: {
126
+ status: 'repaired',
127
+ attempts: attempt,
128
+ maxAttempts,
129
+ transactionId: transactionId || null,
130
+ },
131
+ ...counts,
132
+ };
133
+ }
134
+
135
+ const decisionFailedEntries = currentAppliedIds.length > 0
136
+ ? (batch.entries || [])
137
+ .filter((entry) => currentAppliedIds.includes(entry.id))
138
+ .map((entry) => ({
139
+ id: entry.id,
140
+ reason: repairReason,
141
+ checks: currentFailures,
142
+ candidates: candidatesForEntry(batch, entry.id),
143
+ }))
144
+ : verificationFailuresForEntries(batch, batch.entries || [], repairReason, { checks: currentFailures });
145
+ return {
146
+ applied: [],
147
+ failed: mergeFailedEntries(decisionFailedEntries, currentFailed),
148
+ files: currentFiles,
149
+ cleared: 0,
150
+ count,
151
+ pageUrl,
152
+ warnings: currentWarnings,
153
+ notes: currentNotes,
154
+ reason: 'manual_edit_repair_needs_decision',
155
+ needsManualDecision: true,
156
+ repair: {
157
+ status: 'needs_decision',
158
+ attempts: maxAttempts,
159
+ maxAttempts,
160
+ transactionId: transactionId || null,
161
+ failures: summarizeRepairFailures(currentFailures),
162
+ files: currentFiles,
163
+ },
164
+ ...countByPage(cwd),
165
+ };
166
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Filesystem snapshot/rollback helpers for live-commit-manual-edits.mjs:
3
+ * scanning candidate rollback files, snapshotting their content, detecting
4
+ * what changed, and restoring them when an apply attempt must be undone.
5
+ *
6
+ * Split out of live-commit-manual-edits.mjs. See that file for context.
7
+ */
8
+
9
+ import { isGeneratedFile } from './lib/is-generated.mjs';
10
+ import { readBuffer, writeBuffer } from './live/manual-edits-buffer.mjs';
11
+ import { uniqueStrings } from './live-commit-manual-edits-utils.mjs';
12
+ import { normalizeRollbackPath } from './live-commit-manual-edits-paths.mjs';
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+
16
+ const ROLLBACK_EXTENSIONS = new Set([
17
+ '.astro',
18
+ '.cjs',
19
+ '.css',
20
+ '.htm',
21
+ '.html',
22
+ '.js',
23
+ '.json',
24
+ '.jsx',
25
+ '.md',
26
+ '.mdx',
27
+ '.mjs',
28
+ '.scss',
29
+ '.svelte',
30
+ '.svg',
31
+ '.ts',
32
+ '.tsx',
33
+ '.txt',
34
+ '.vue',
35
+ '.yaml',
36
+ '.yml',
37
+ ]);
38
+ const ROLLBACK_SKIP_DIRS = new Set([
39
+ '.astro',
40
+ '.git',
41
+ '.monodesign',
42
+ '.next',
43
+ '.nuxt',
44
+ '.svelte-kit',
45
+ 'build',
46
+ 'coverage',
47
+ 'dist',
48
+ 'node_modules',
49
+ 'out',
50
+ ]);
51
+
52
+ export function clearAppliedEntries(cwd, appliedEntryIds) {
53
+ const ids = new Set(appliedEntryIds);
54
+ if (ids.size === 0) return 0;
55
+ const buffer = readBuffer(cwd);
56
+ let cleared = 0;
57
+ const kept = [];
58
+ for (const entry of buffer.entries || []) {
59
+ if (ids.has(entry.id)) {
60
+ cleared += Array.isArray(entry.ops) ? entry.ops.length : 0;
61
+ } else {
62
+ kept.push(entry);
63
+ }
64
+ }
65
+ writeBuffer(cwd, { version: buffer.version || 1, entries: kept });
66
+ return cleared;
67
+ }
68
+
69
+ export function snapshotRollbackFiles(cwd, files = null) {
70
+ const snapshot = new Map();
71
+ const rollbackFiles = Array.isArray(files) && files.length > 0
72
+ ? uniqueStrings(files).map((file) => normalizeRollbackPath(cwd, file)).filter(Boolean)
73
+ : collectRollbackFiles(cwd);
74
+ for (const relativeFile of rollbackFiles) {
75
+ const absolute = path.resolve(cwd, relativeFile);
76
+ try {
77
+ snapshot.set(relativeFile, {
78
+ existed: true,
79
+ content: fs.readFileSync(absolute, 'utf-8'),
80
+ });
81
+ } catch (err) {
82
+ if (err?.code === 'ENOENT') {
83
+ snapshot.set(relativeFile, { existed: false });
84
+ }
85
+ // Other read failures are not safe to roll back.
86
+ }
87
+ }
88
+ return snapshot;
89
+ }
90
+
91
+ function collectRollbackFiles(cwd) {
92
+ const out = [];
93
+ const seenDirs = new Set();
94
+ const seenFiles = new Set();
95
+ scanRollbackDir(cwd, cwd, out, seenDirs, seenFiles, 0);
96
+ return out;
97
+ }
98
+
99
+ function scanRollbackDir(dir, cwd, out, seenDirs, seenFiles, depth) {
100
+ if (depth > 10) return;
101
+ let realDir;
102
+ try { realDir = fs.realpathSync(dir); } catch { return; }
103
+ if (seenDirs.has(realDir)) return;
104
+ seenDirs.add(realDir);
105
+
106
+ let entries;
107
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
108
+ for (const entry of entries) {
109
+ if (entry.isDirectory()) {
110
+ if (ROLLBACK_SKIP_DIRS.has(entry.name)) continue;
111
+ scanRollbackDir(path.join(dir, entry.name), cwd, out, seenDirs, seenFiles, depth + 1);
112
+ continue;
113
+ }
114
+ if (!entry.isFile()) continue;
115
+ if (!ROLLBACK_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) continue;
116
+ const absolute = path.join(dir, entry.name);
117
+ if (isGeneratedFile(absolute, { cwd })) continue;
118
+ let realFile;
119
+ try { realFile = fs.realpathSync(absolute); } catch { continue; }
120
+ if (seenFiles.has(realFile)) continue;
121
+ seenFiles.add(realFile);
122
+ const relative = path.relative(cwd, absolute).split(path.sep).join('/');
123
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) continue;
124
+ out.push(relative);
125
+ }
126
+ }
127
+
128
+ function changedFilesSinceSnapshot(cwd, snapshot, scopeFiles = null) {
129
+ const changed = new Map();
130
+ const scopedFiles = Array.isArray(scopeFiles) && scopeFiles.length > 0
131
+ ? scopeFiles.map((file) => normalizeRollbackPath(cwd, file)).filter(Boolean)
132
+ : null;
133
+ const currentFiles = new Set(scopedFiles || collectRollbackFiles(cwd));
134
+ for (const [relativeFile, before] of snapshot.entries()) {
135
+ if (scopedFiles && !currentFiles.has(relativeFile)) continue;
136
+ const absolute = path.resolve(cwd, relativeFile);
137
+ if (before?.existed === false) {
138
+ if (fs.existsSync(absolute)) changed.set(relativeFile, { file: relativeFile, kind: 'added' });
139
+ continue;
140
+ }
141
+ if (!fs.existsSync(absolute)) {
142
+ changed.set(relativeFile, { file: relativeFile, kind: 'deleted' });
143
+ continue;
144
+ }
145
+ let content;
146
+ try { content = fs.readFileSync(absolute, 'utf-8'); } catch { continue; }
147
+ if (content !== before.content) {
148
+ changed.set(relativeFile, { file: relativeFile, kind: 'modified' });
149
+ }
150
+ }
151
+ for (const relativeFile of currentFiles) {
152
+ if (!snapshot.has(relativeFile)) {
153
+ changed.set(relativeFile, { file: relativeFile, kind: 'unknown' });
154
+ }
155
+ }
156
+ return [...changed.values()];
157
+ }
158
+
159
+ export function rollbackChangedFiles(cwd, snapshot, extraFiles = [], scopeFiles = []) {
160
+ const scope = new Set(
161
+ [...(scopeFiles || []), ...(extraFiles || [])]
162
+ .map((file) => normalizeRollbackPath(cwd, file))
163
+ .filter(Boolean),
164
+ );
165
+ const changed = changedFilesSinceSnapshot(cwd, snapshot, [...scope]);
166
+ const byFile = new Map(changed.map((item) => [item.file, item]));
167
+ for (const file of extraFiles || []) {
168
+ const relative = normalizeRollbackPath(cwd, file);
169
+ if (relative && !byFile.has(relative)) {
170
+ byFile.set(relative, { file: relative, kind: snapshot.has(relative) ? 'reported' : 'unknown' });
171
+ }
172
+ }
173
+
174
+ const rolledBackFiles = [];
175
+ const rollbackFailures = [];
176
+ for (const item of byFile.values()) {
177
+ if (!scope.has(item.file)) continue;
178
+ const absolute = path.resolve(cwd, item.file);
179
+ const before = snapshot.get(item.file);
180
+ try {
181
+ if (before?.existed !== false && typeof before?.content === 'string') {
182
+ fs.mkdirSync(path.dirname(absolute), { recursive: true });
183
+ fs.writeFileSync(absolute, before.content, 'utf-8');
184
+ } else if (before?.existed === false && item.kind === 'added' && fs.existsSync(absolute)) {
185
+ fs.rmSync(absolute);
186
+ } else {
187
+ rollbackFailures.push({ file: item.file, reason: 'no_snapshot' });
188
+ continue;
189
+ }
190
+ rolledBackFiles.push(item.file);
191
+ } catch (err) {
192
+ rollbackFailures.push({ file: item.file, reason: 'restore_failed', message: err.message || String(err) });
193
+ }
194
+ }
195
+ return { rolledBackFiles, rollbackFailures };
196
+ }
197
+
198
+ export function collectApplyOwnedFiles(batch, cwd, extraFiles = []) {
199
+ const files = [];
200
+ for (const entry of batch?.entries || []) {
201
+ for (const op of entry.ops || []) files.push(op.sourceHint?.file);
202
+ }
203
+ for (const candidate of batch?.candidates || []) {
204
+ files.push(candidate.sourceHint?.relativeFile, candidate.sourceHint?.file);
205
+ for (const item of candidate.textMatches || []) files.push(item.file);
206
+ for (const item of candidate.objectKeyMatches || []) files.push(item.file);
207
+ for (const item of candidate.locatorMatches || []) files.push(item.file);
208
+ for (const item of candidate.contextTextMatches || []) files.push(item.file);
209
+ }
210
+ files.push(...(extraFiles || []));
211
+ return uniqueStrings(files)
212
+ .map((file) => normalizeRollbackPath(cwd, file))
213
+ .filter(Boolean);
214
+ }
215
+
216
+ export function unreportedChangedFiles(cwd, snapshot, reportedFiles, scopeFiles = []) {
217
+ const reported = new Set(
218
+ (reportedFiles || [])
219
+ .map((file) => normalizeRollbackPath(cwd, file))
220
+ .filter(Boolean),
221
+ );
222
+ const scope = new Set(
223
+ (scopeFiles || [])
224
+ .map((file) => normalizeRollbackPath(cwd, file))
225
+ .filter(Boolean),
226
+ );
227
+ return changedFilesSinceSnapshot(cwd, snapshot, [...scope])
228
+ .map((item) => item.file)
229
+ .filter((file) => scope.has(file))
230
+ .filter((file) => !reported.has(file));
231
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Small generic helpers for live-commit-manual-edits.mjs: CLI arg parsing,
3
+ * batch/entry bookkeeping, and repair-attempt bookkeeping.
4
+ *
5
+ * Split out of live-commit-manual-edits.mjs. See that file for context.
6
+ */
7
+
8
+ const DEFAULT_REPAIR_ATTEMPTS = 3;
9
+
10
+ export function argVal(args, name) {
11
+ const prefix = `${name}=`;
12
+ for (const arg of args) {
13
+ if (arg === name) return true;
14
+ if (arg.startsWith(prefix)) return arg.slice(prefix.length);
15
+ }
16
+ return null;
17
+ }
18
+
19
+ export function countOps(entries) {
20
+ let count = 0;
21
+ for (const entry of entries || []) count += Array.isArray(entry.ops) ? entry.ops.length : 0;
22
+ return count;
23
+ }
24
+
25
+ export function summarizeAppliedEntries(entries, appliedEntryIds) {
26
+ const ids = new Set(appliedEntryIds);
27
+ const out = [];
28
+ for (const entry of entries || []) {
29
+ if (!ids.has(entry.id)) continue;
30
+ for (const op of entry.ops || []) {
31
+ out.push({
32
+ id: entry.id,
33
+ ref: op.ref,
34
+ originalText: op.originalText,
35
+ newText: op.newText,
36
+ });
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+
42
+ export function normalizeFailedEntries(batch, result, fallbackReason) {
43
+ const failed = [];
44
+ const failedByEntryId = new Map();
45
+ for (const item of result?.failed || []) {
46
+ const entryId = item.entryId || item.id || null;
47
+ if (!entryId) continue;
48
+ failedByEntryId.set(entryId, item);
49
+ }
50
+
51
+ for (const entry of batch.entries || []) {
52
+ const item = failedByEntryId.get(entry.id);
53
+ if (!item) continue;
54
+ failed.push({
55
+ id: entry.id,
56
+ reason: item.reason || item.message || fallbackReason || 'failed',
57
+ candidates: Array.isArray(item.candidates) && item.candidates.length > 0
58
+ ? item.candidates
59
+ : candidatesForEntry(batch, entry.id),
60
+ });
61
+ }
62
+ return failed;
63
+ }
64
+
65
+ export function mergeFailedEntries(...groups) {
66
+ const out = [];
67
+ const indexById = new Map();
68
+ for (const item of groups.flatMap((group) => Array.isArray(group) ? group : [])) {
69
+ if (!item || typeof item !== 'object') continue;
70
+ const id = typeof item.id === 'string' && item.id ? item.id : null;
71
+ if (!id) {
72
+ out.push(item);
73
+ continue;
74
+ }
75
+ const existingIndex = indexById.get(id);
76
+ if (existingIndex === undefined) {
77
+ indexById.set(id, out.length);
78
+ out.push(item);
79
+ continue;
80
+ }
81
+ out[existingIndex] = {
82
+ ...out[existingIndex],
83
+ ...item,
84
+ candidates: item.candidates || out[existingIndex].candidates,
85
+ checks: item.checks || out[existingIndex].checks,
86
+ };
87
+ }
88
+ return out;
89
+ }
90
+
91
+ export function candidatesForEntry(batch, entryId) {
92
+ return (batch.candidates || [])
93
+ .filter((candidate) => candidate.entryId === entryId)
94
+ .flatMap((candidate) => [
95
+ ...(candidate.sourceHint ? [candidate.sourceHint] : []),
96
+ ...(candidate.textMatches || []),
97
+ ...(candidate.objectKeyMatches || []),
98
+ ...(candidate.locatorMatches || []),
99
+ ...(candidate.contextTextMatches || []),
100
+ ])
101
+ .slice(0, 12);
102
+ }
103
+
104
+ export function uniqueStrings(values) {
105
+ return [...new Set(values.filter((value) => typeof value === 'string' && value.trim()))];
106
+ }
107
+
108
+ export function allEntryIds(batch) {
109
+ return (batch?.entries || []).map((entry) => entry.id).filter(Boolean);
110
+ }
111
+
112
+ export function mergeUniqueStrings(...groups) {
113
+ return uniqueStrings(groups.flatMap((group) => Array.isArray(group) ? group : []));
114
+ }
115
+
116
+ export function repairAttemptLimit(env = process.env) {
117
+ const value = Number(env.MONODESIGN_LIVE_MANUAL_EDIT_REPAIR_ATTEMPTS || DEFAULT_REPAIR_ATTEMPTS);
118
+ if (!Number.isFinite(value)) return DEFAULT_REPAIR_ATTEMPTS;
119
+ return Math.max(1, Math.min(10, Math.trunc(value)));
120
+ }
121
+
122
+ export function summarizeRepairFailures(failures = []) {
123
+ return failures.map((failure) => {
124
+ const out = {
125
+ reason: failure.reason || failure.detail || 'validation_failed',
126
+ };
127
+ if (failure.id || failure.entryId) out.entryId = failure.id || failure.entryId;
128
+ if (failure.ref) out.ref = failure.ref;
129
+ if (failure.detail) out.detail = failure.detail;
130
+ if (failure.file) out.file = failure.file;
131
+ if (failure.message) out.message = failure.message;
132
+ if (failure.marker) out.marker = failure.marker;
133
+ if (Array.isArray(failure.files)) out.files = failure.files.slice(0, 8);
134
+ if (Array.isArray(failure.candidates)) {
135
+ out.candidates = failure.candidates.slice(0, 8).map((candidate) => ({
136
+ file: candidate.file,
137
+ line: candidate.line,
138
+ kind: candidate.kind,
139
+ reason: candidate.reason,
140
+ }));
141
+ }
142
+ if (Array.isArray(failure.failures)) {
143
+ out.failures = failure.failures.slice(0, 8).map((item) => ({
144
+ ref: item.ref,
145
+ reason: item.reason || item.detail,
146
+ detail: item.detail,
147
+ candidates: Array.isArray(item.candidates)
148
+ ? item.candidates.slice(0, 6).map((candidate) => ({
149
+ file: candidate.file,
150
+ line: candidate.line,
151
+ kind: candidate.kind,
152
+ reason: candidate.reason,
153
+ }))
154
+ : undefined,
155
+ }));
156
+ }
157
+ if (failure.checks) out.checks = failure.checks;
158
+ return out;
159
+ }).slice(0, 20);
160
+ }
161
+
162
+ export function buildRepairBatch(batch, repair) {
163
+ return {
164
+ ...batch,
165
+ repair,
166
+ };
167
+ }