sela-core 1.0.7 → 1.0.8
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/ErrorHandler.d.ts +1 -1
- package/dist/cli/ErrorHandler.js +14 -14
- package/dist/cli/commands/bulk.d.ts +1 -1
- package/dist/cli/commands/bulk.d.ts.map +1 -1
- package/dist/cli/commands/bulk.js +51 -33
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +26 -18
- package/dist/cli/commands/showReport.d.ts +1 -1
- package/dist/cli/commands/showReport.d.ts.map +1 -1
- package/dist/cli/commands/showReport.js +12 -10
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +8 -8
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +38 -30
- package/dist/cli/index.js +9 -9
- package/dist/cli/ui/DnaTable.d.ts +1 -1
- package/dist/cli/ui/DnaTable.d.ts.map +1 -1
- package/dist/cli/ui/DnaTable.js +23 -16
- package/dist/cli/ui/RefactorWizard.d.ts +3 -3
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
- package/dist/cli/ui/RefactorWizard.js +108 -82
- package/dist/config/ConfigLoader.d.ts +2 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +15 -15
- package/dist/config/DryRunGuard.d.ts +1 -1
- package/dist/config/DryRunGuard.js +7 -7
- package/dist/config/SelaConfig.d.ts +12 -16
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +32 -26
- package/dist/engine/HealingRegistry.d.ts +1 -1
- package/dist/engine/HealingRegistry.js +3 -3
- package/dist/engine/SelaEngine.d.ts +1 -1
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +63 -49
- package/dist/errors/SelaError.d.ts +0 -1
- package/dist/errors/SelaError.d.ts.map +1 -1
- package/dist/errors/SelaError.js +6 -7
- package/dist/fixtures/expectProxy.d.ts +1 -1
- package/dist/fixtures/expectProxy.js +11 -11
- package/dist/fixtures/index.js +14 -14
- package/dist/fixtures/proxyTag.d.ts +1 -1
- package/dist/fixtures/proxyTag.js +3 -3
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/reporter/SelaReporter.d.ts +45 -12
- package/dist/reporter/SelaReporter.d.ts.map +1 -1
- package/dist/reporter/SelaReporter.js +139 -39
- package/dist/services/ASTSourceUpdater.d.ts +3 -3
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +92 -68
- package/dist/services/ArgumentTypeAnalyzer.js +7 -7
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
- package/dist/services/BlastRadiusAnalyzer.js +5 -2
- package/dist/services/ChainValidator.js +22 -22
- package/dist/services/CrossFileHealer.js +7 -7
- package/dist/services/DefinitionTracer.d.ts +1 -1
- package/dist/services/DefinitionTracer.d.ts.map +1 -1
- package/dist/services/DefinitionTracer.js +28 -13
- package/dist/services/DnaEditorService.d.ts +2 -2
- package/dist/services/DnaEditorService.d.ts.map +1 -1
- package/dist/services/DnaEditorService.js +81 -62
- package/dist/services/DnaIndexService.d.ts +1 -1
- package/dist/services/DnaIndexService.d.ts.map +1 -1
- package/dist/services/DnaIndexService.js +14 -14
- package/dist/services/HealReportService.d.ts +6 -6
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +33 -29
- package/dist/services/HealingAdvisory.js +1 -1
- package/dist/services/HealingCacheService.d.ts +8 -8
- package/dist/services/HealingCacheService.d.ts.map +1 -1
- package/dist/services/HealingCacheService.js +24 -24
- package/dist/services/HealthReportService.d.ts +1 -1
- package/dist/services/HealthReportService.d.ts.map +1 -1
- package/dist/services/HealthReportService.js +25 -25
- package/dist/services/InitializerUpdater.js +2 -2
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +32 -21
- package/dist/services/InteractiveReview.d.ts +8 -17
- package/dist/services/InteractiveReview.d.ts.map +1 -1
- package/dist/services/InteractiveReview.js +31 -124
- package/dist/services/LLMService.d.ts +1 -1
- package/dist/services/LLMService.js +16 -16
- package/dist/services/PRAutomationService.d.ts +2 -2
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +43 -39
- package/dist/services/PendingPromptLedger.d.ts +2 -2
- package/dist/services/PendingPromptLedger.js +6 -6
- package/dist/services/ReportGenerator.d.ts +2 -2
- package/dist/services/ReportGenerator.d.ts.map +1 -1
- package/dist/services/ReportGenerator.js +5 -5
- package/dist/services/SafetyGuard.d.ts +3 -6
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +29 -106
- package/dist/services/SnapshotService.js +5 -5
- package/dist/services/SourceLinkService.d.ts +2 -2
- package/dist/services/SourceLinkService.d.ts.map +1 -1
- package/dist/services/SourceLinkService.js +31 -22
- package/dist/services/SourceUpdater.d.ts +1 -1
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +27 -26
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- package/dist/services/WorkspaceSnapshotService.d.ts +6 -6
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -1
- package/dist/services/WorkspaceSnapshotService.js +10 -12
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/IsolatedDiff.d.ts +2 -2
- package/dist/utils/IsolatedDiff.d.ts.map +1 -1
- package/dist/utils/IsolatedDiff.js +13 -10
- package/package.json +2 -2
|
@@ -10,12 +10,11 @@ const SelaError_1 = require("../errors/SelaError");
|
|
|
10
10
|
const DEFAULT_THRESHOLDS = {
|
|
11
11
|
confidenceHardStop: 85,
|
|
12
12
|
confidenceReviewThreshold: 95,
|
|
13
|
-
assertionSimilarityFloor: 0.7,
|
|
14
13
|
auditorConfidenceMin: 75,
|
|
15
14
|
allowSuspicious: false,
|
|
16
15
|
auditorStrictness: "balanced",
|
|
17
16
|
};
|
|
18
|
-
// Binary semantic opposites
|
|
17
|
+
// Binary semantic opposites - always trigger FAIL_HARD regardless of mode,
|
|
19
18
|
// because they indicate a state inversion (pass→fail, success→error, etc.)
|
|
20
19
|
// that almost certainly masks a real logical regression.
|
|
21
20
|
const SEMANTIC_OPPOSITE_PAIRS = new Set([
|
|
@@ -190,7 +189,7 @@ const ROLE_KEYWORDS = new Map([
|
|
|
190
189
|
],
|
|
191
190
|
]);
|
|
192
191
|
// Pre-compiled word-boundary regexes for the role classifier. Built
|
|
193
|
-
// once at module load
|
|
192
|
+
// once at module load - classifyFunctionalRole() is on the hot heal
|
|
194
193
|
// path, so we don't pay for regex construction per call.
|
|
195
194
|
const ROLE_KEYWORD_REGEXES = Array.from(ROLE_KEYWORDS, ([role, keywords]) => ({
|
|
196
195
|
role,
|
|
@@ -242,7 +241,7 @@ class SafetyGuard {
|
|
|
242
241
|
return {
|
|
243
242
|
level: "FAIL_HARD",
|
|
244
243
|
code: SelaError_1.SAFETY_GUARD_CODES.FAIL_HARD_VISIBILITY_GHOST,
|
|
245
|
-
reason: `Visibility Gate: candidate element is a Ghost (${vis.ghostReason})
|
|
244
|
+
reason: `Visibility Gate: candidate element is a Ghost (${vis.ghostReason}) - ` +
|
|
246
245
|
`functionally absent from the user's perspective`,
|
|
247
246
|
canProceed: false,
|
|
248
247
|
};
|
|
@@ -253,12 +252,12 @@ class SafetyGuard {
|
|
|
253
252
|
return {
|
|
254
253
|
level: "FAIL_HARD",
|
|
255
254
|
code: SelaError_1.SAFETY_GUARD_CODES.FAIL_HARD_VISIBILITY_OCCLUDED,
|
|
256
|
-
reason: `Visibility Gate: candidate element is occluded by an overlay on branch "${context.branch}"
|
|
255
|
+
reason: `Visibility Gate: candidate element is occluded by an overlay on branch "${context.branch}" - ` +
|
|
257
256
|
`hard-fail policy active on main`,
|
|
258
257
|
canProceed: false,
|
|
259
258
|
};
|
|
260
259
|
}
|
|
261
|
-
console.warn(`[SafetyGuard] ⚠️ Occlusion detected on branch "${context.branch ?? "unknown"}"
|
|
260
|
+
console.warn(`[SafetyGuard] ⚠️ Occlusion detected on branch "${context.branch ?? "unknown"}" - ` +
|
|
262
261
|
`escalating to REQUIRES_REVIEW (not hard-failing on non-main branch)`);
|
|
263
262
|
return {
|
|
264
263
|
level: "REQUIRES_REVIEW",
|
|
@@ -273,7 +272,7 @@ class SafetyGuard {
|
|
|
273
272
|
return {
|
|
274
273
|
level: "BLOCKED",
|
|
275
274
|
code: SelaError_1.SAFETY_GUARD_CODES.BLOCKED_LOW_CONFIDENCE,
|
|
276
|
-
reason: `Confidence too low (${confidence}/100 < ${this.thresholds.confidenceHardStop})
|
|
275
|
+
reason: `Confidence too low (${confidence}/100 < ${this.thresholds.confidenceHardStop}) - refusing to heal`,
|
|
277
276
|
canProceed: false,
|
|
278
277
|
};
|
|
279
278
|
}
|
|
@@ -284,16 +283,18 @@ class SafetyGuard {
|
|
|
284
283
|
//
|
|
285
284
|
// When liveText + dnaBaselineText are both available and differ, we
|
|
286
285
|
// synthesise a contentChange pair and apply the full inversion-check
|
|
287
|
-
// pipeline on it
|
|
286
|
+
// pipeline on it - regardless of what the AI reported.
|
|
288
287
|
//
|
|
289
288
|
// If the AI DID report contentChange we use that (more precise than raw
|
|
290
289
|
// innerText, which may include child element noise).
|
|
291
290
|
let effectiveContentChange = aiFix.contentChange;
|
|
292
|
-
if (!effectiveContentChange &&
|
|
291
|
+
if (!effectiveContentChange &&
|
|
292
|
+
liveText !== undefined &&
|
|
293
|
+
dnaBaselineText !== undefined) {
|
|
293
294
|
const normLive = liveText.trim();
|
|
294
295
|
const normDNA = dnaBaselineText.trim();
|
|
295
296
|
if (normLive !== normDNA && normLive.length > 0 && normDNA.length > 0) {
|
|
296
|
-
console.warn(`[SafetyGuard] 🚨 Zero-Trust override
|
|
297
|
+
console.warn(`[SafetyGuard] 🚨 Zero-Trust override - AI omitted contentChange but live DOM differs:\n` +
|
|
297
298
|
` DNA baseline : "${normDNA}"\n` +
|
|
298
299
|
` Live text : "${normLive}"`);
|
|
299
300
|
effectiveContentChange = { oldText: normDNA, newText: normLive };
|
|
@@ -301,7 +302,9 @@ class SafetyGuard {
|
|
|
301
302
|
}
|
|
302
303
|
// ── 3. Structural-only heal (no text change detected) ────────
|
|
303
304
|
if (!effectiveContentChange) {
|
|
304
|
-
const level = confidence < this.thresholds.confidenceReviewThreshold
|
|
305
|
+
const level = confidence < this.thresholds.confidenceReviewThreshold
|
|
306
|
+
? "REQUIRES_REVIEW"
|
|
307
|
+
: "SAFE";
|
|
305
308
|
return {
|
|
306
309
|
level,
|
|
307
310
|
reason: `Structural selector change only (confidence=${confidence})`,
|
|
@@ -313,12 +316,12 @@ class SafetyGuard {
|
|
|
313
316
|
console.log(`[SafetyGuard] 🔬 Content change (${source}): "${oldText}" → "${newText}"`);
|
|
314
317
|
// ── 4. Binary semantic-opposite guard ────────────────────────
|
|
315
318
|
// Applies in ALL modes: "Enabled"→"Disabled", "Success"→"Error", etc.
|
|
316
|
-
// These always indicate a state inversion
|
|
319
|
+
// These always indicate a state inversion - never safe to auto-heal.
|
|
317
320
|
if (this.isSemanticOpposite(oldText, newText)) {
|
|
318
321
|
return {
|
|
319
322
|
level: "FAIL_HARD",
|
|
320
323
|
code: SelaError_1.SAFETY_GUARD_CODES.FAIL_HARD_SEMANTIC_INVERSION,
|
|
321
|
-
reason: `Semantic inversion detected: "${oldText}" ↔ "${newText}"
|
|
324
|
+
reason: `Semantic inversion detected: "${oldText}" ↔ "${newText}" - ` +
|
|
322
325
|
`state inversions mask logical regressions`,
|
|
323
326
|
canProceed: false,
|
|
324
327
|
};
|
|
@@ -334,30 +337,28 @@ class SafetyGuard {
|
|
|
334
337
|
return {
|
|
335
338
|
level: "FAIL_HARD",
|
|
336
339
|
code: SelaError_1.SAFETY_GUARD_CODES.FAIL_HARD_STATUS_INVERSION,
|
|
337
|
-
reason: `Status inversion: "${oldText}" (${oldRole}) → "${newText}" (${newRole})
|
|
340
|
+
reason: `Status inversion: "${oldText}" (${oldRole}) → "${newText}" (${newRole}) - ` +
|
|
338
341
|
`masking a logic failure is not permitted`,
|
|
339
342
|
canProceed: false,
|
|
340
343
|
};
|
|
341
344
|
}
|
|
342
345
|
// Cross-role swap (e.g. DELETE → CANCEL, SUBMIT → NAVIGATE)
|
|
343
|
-
if (oldRole !== "UNKNOWN" &&
|
|
344
|
-
newRole !== "UNKNOWN" &&
|
|
345
|
-
oldRole !== newRole) {
|
|
346
|
+
if (oldRole !== "UNKNOWN" && newRole !== "UNKNOWN" && oldRole !== newRole) {
|
|
346
347
|
if (mode === "assertion") {
|
|
347
348
|
// Assertion explicitly checked the old functional text; a different
|
|
348
349
|
// function being found is almost certainly a wrong element fix.
|
|
349
350
|
return {
|
|
350
351
|
level: "BLOCKED",
|
|
351
352
|
code: SelaError_1.SAFETY_GUARD_CODES.BLOCKED_ROLE_SWAP_ASSERTION,
|
|
352
|
-
reason: `Functional role swap in assertion: "${oldText}" (${oldRole}) → "${newText}" (${newRole})
|
|
353
|
+
reason: `Functional role swap in assertion: "${oldText}" (${oldRole}) → "${newText}" (${newRole}) - ` +
|
|
353
354
|
`the assertion was verifying a specific functional state`,
|
|
354
355
|
canProceed: false,
|
|
355
356
|
};
|
|
356
357
|
}
|
|
357
|
-
// Action mode: allow with warning
|
|
358
|
+
// Action mode: allow with warning - a UI refactor may rename a control
|
|
358
359
|
// (e.g. "Delete" button renamed "Remove" or merged into a menu)
|
|
359
360
|
console.warn(`[SafetyGuard] ⚠️ Functional role change in action: ` +
|
|
360
|
-
`"${oldText}" (${oldRole}) → "${newText}" (${newRole})
|
|
361
|
+
`"${oldText}" (${oldRole}) → "${newText}" (${newRole}) - review recommended`);
|
|
361
362
|
return {
|
|
362
363
|
level: "REQUIRES_REVIEW",
|
|
363
364
|
reason: `Functional role change in action: "${oldText}" (${oldRole}) → "${newText}" (${newRole})`,
|
|
@@ -392,7 +393,7 @@ class SafetyGuard {
|
|
|
392
393
|
return {
|
|
393
394
|
level: "FAIL_HARD",
|
|
394
395
|
code: SelaError_1.SAFETY_GUARD_CODES.FAIL_HARD_AUDITOR_INCONSISTENT,
|
|
395
|
-
reason: `Intent Auditor
|
|
396
|
+
reason: `Intent Auditor - INCONSISTENT: ${auditVerdict.reason}` +
|
|
396
397
|
(auditVerdict.inversionType
|
|
397
398
|
? ` [inversion: ${auditVerdict.inversionType}]`
|
|
398
399
|
: ""),
|
|
@@ -409,23 +410,23 @@ class SafetyGuard {
|
|
|
409
410
|
return {
|
|
410
411
|
level: "BLOCKED",
|
|
411
412
|
code: SelaError_1.SAFETY_GUARD_CODES.BLOCKED_AUDITOR_SUSPICIOUS,
|
|
412
|
-
reason: `Intent Auditor
|
|
413
|
+
reason: `Intent Auditor - SUSPICIOUS in assertion mode: ${auditVerdict.reason}`,
|
|
413
414
|
canProceed: false,
|
|
414
415
|
meta,
|
|
415
416
|
};
|
|
416
417
|
}
|
|
417
418
|
if (this.thresholds.allowSuspicious && mode === "assertion") {
|
|
418
|
-
console.log(`[SafetyGuard] ℹ️ allowSuspicious=true
|
|
419
|
+
console.log(`[SafetyGuard] ℹ️ allowSuspicious=true - SUSPICIOUS in assertion mode escalated to REQUIRES_REVIEW`);
|
|
419
420
|
}
|
|
420
421
|
return {
|
|
421
422
|
level: "REQUIRES_REVIEW",
|
|
422
|
-
reason: `Intent Auditor
|
|
423
|
+
reason: `Intent Auditor - SUSPICIOUS in ${mode} mode: ${auditVerdict.reason}`,
|
|
423
424
|
canProceed: true,
|
|
424
425
|
meta,
|
|
425
426
|
};
|
|
426
427
|
}
|
|
427
|
-
// CONSISTENT
|
|
428
|
-
console.log(`[SafetyGuard] ✅ Intent Auditor
|
|
428
|
+
// CONSISTENT - fall through to confidence tiers
|
|
429
|
+
console.log(`[SafetyGuard] ✅ Intent Auditor - CONSISTENT (${auditVerdict.confidence}%): ${auditVerdict.reason}`);
|
|
429
430
|
if (auditVerdict.scoreBreakdown) {
|
|
430
431
|
const { rawConfidence, penalty, bonus } = auditVerdict.scoreBreakdown;
|
|
431
432
|
if (penalty !== 0 || bonus !== 0) {
|
|
@@ -440,32 +441,11 @@ class SafetyGuard {
|
|
|
440
441
|
}
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
|
-
// ── 8.
|
|
444
|
-
// For same-role pairs (e.g. SUBMIT→SUBMIT: "Submit"→"Send") we skip
|
|
445
|
-
// the similarity gate entirely — the role match is sufficient proof
|
|
446
|
-
// of functional equivalence.
|
|
447
|
-
//
|
|
448
|
-
// The gate is only applied when BOTH roles are UNKNOWN in assertion
|
|
449
|
-
// mode, where we have no functional category to fall back on.
|
|
450
|
-
if (mode === "assertion" && oldRole === "UNKNOWN" && newRole === "UNKNOWN") {
|
|
451
|
-
const similarity = this.computeSemanticSimilarity(oldText, newText);
|
|
452
|
-
console.log(`[SafetyGuard] 📊 Similarity: "${oldText}" ↔ "${newText}" = ${(similarity * 100).toFixed(1)}%`);
|
|
453
|
-
if (similarity < this.thresholds.assertionSimilarityFloor) {
|
|
454
|
-
return {
|
|
455
|
-
level: "BLOCKED",
|
|
456
|
-
code: SelaError_1.SAFETY_GUARD_CODES.BLOCKED_SIMILARITY_FLOOR,
|
|
457
|
-
reason: `Text divergence too high in assertion: "${oldText}" → "${newText}" ` +
|
|
458
|
-
`(similarity=${(similarity * 100).toFixed(1)}% < ${(this.thresholds.assertionSimilarityFloor * 100).toFixed(0)}%) — ` +
|
|
459
|
-
`possible content regression`,
|
|
460
|
-
canProceed: false,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// ── 9. Confidence tiers ──────────────────────────────────────
|
|
444
|
+
// ── 8. Confidence tiers ──────────────────────────────────────
|
|
465
445
|
if (confidence < this.thresholds.confidenceReviewThreshold) {
|
|
466
446
|
return {
|
|
467
447
|
level: "REQUIRES_REVIEW",
|
|
468
|
-
reason: `Low-confidence fix (${confidence}/100)
|
|
448
|
+
reason: `Low-confidence fix (${confidence}/100) - tagging for manual review`,
|
|
469
449
|
canProceed: true,
|
|
470
450
|
meta: localAuditMeta,
|
|
471
451
|
};
|
|
@@ -515,62 +495,5 @@ class SafetyGuard {
|
|
|
515
495
|
}
|
|
516
496
|
return "UNKNOWN";
|
|
517
497
|
}
|
|
518
|
-
// ─────────────────────────────────────────────────────────────
|
|
519
|
-
// SEMANTIC SIMILARITY (no external dependencies)
|
|
520
|
-
//
|
|
521
|
-
// Combined score: 0.5 × levenshtein_similarity + 0.5 × jaccard_similarity
|
|
522
|
-
//
|
|
523
|
-
// Levenshtein captures character-level similarity (typos, abbreviations).
|
|
524
|
-
// Jaccard captures word-level overlap (shared vocabulary, different order).
|
|
525
|
-
// The 50/50 blend is robust against short labels where one metric misleads.
|
|
526
|
-
// ─────────────────────────────────────────────────────────────
|
|
527
|
-
computeSemanticSimilarity(a, b) {
|
|
528
|
-
const na = a.toLowerCase().trim();
|
|
529
|
-
const nb = b.toLowerCase().trim();
|
|
530
|
-
if (na === nb)
|
|
531
|
-
return 1;
|
|
532
|
-
const maxLen = Math.max(na.length, nb.length);
|
|
533
|
-
const levSim = maxLen === 0 ? 1 : 1 - this.levenshteinDistance(na, nb) / maxLen;
|
|
534
|
-
const jaccard = this.jaccardSimilarity(na, nb);
|
|
535
|
-
return 0.5 * levSim + 0.5 * jaccard;
|
|
536
|
-
}
|
|
537
|
-
// ─────────────────────────────────────────────────────────────
|
|
538
|
-
// PRIVATE: Levenshtein distance (space-optimised two-row DP)
|
|
539
|
-
// ─────────────────────────────────────────────────────────────
|
|
540
|
-
levenshteinDistance(a, b) {
|
|
541
|
-
const m = a.length;
|
|
542
|
-
const n = b.length;
|
|
543
|
-
if (m === 0)
|
|
544
|
-
return n;
|
|
545
|
-
if (n === 0)
|
|
546
|
-
return m;
|
|
547
|
-
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
548
|
-
let curr = new Array(n + 1);
|
|
549
|
-
for (let i = 1; i <= m; i++) {
|
|
550
|
-
curr[0] = i;
|
|
551
|
-
for (let j = 1; j <= n; j++) {
|
|
552
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
553
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
554
|
-
}
|
|
555
|
-
[prev, curr] = [curr, prev];
|
|
556
|
-
}
|
|
557
|
-
return prev[n];
|
|
558
|
-
}
|
|
559
|
-
// ─────────────────────────────────────────────────────────────
|
|
560
|
-
// PRIVATE: Jaccard word-set similarity
|
|
561
|
-
// ─────────────────────────────────────────────────────────────
|
|
562
|
-
jaccardSimilarity(a, b) {
|
|
563
|
-
const setA = new Set(a.split(/\s+/).filter(Boolean));
|
|
564
|
-
const setB = new Set(b.split(/\s+/).filter(Boolean));
|
|
565
|
-
if (setA.size === 0 && setB.size === 0)
|
|
566
|
-
return 1;
|
|
567
|
-
let intersection = 0;
|
|
568
|
-
for (const w of setA) {
|
|
569
|
-
if (setB.has(w))
|
|
570
|
-
intersection++;
|
|
571
|
-
}
|
|
572
|
-
const union = setA.size + setB.size - intersection;
|
|
573
|
-
return union === 0 ? 1 : intersection / union;
|
|
574
|
-
}
|
|
575
498
|
}
|
|
576
499
|
exports.SafetyGuard = SafetyGuard;
|
|
@@ -67,12 +67,12 @@ class SnapshotService {
|
|
|
67
67
|
return raw;
|
|
68
68
|
}
|
|
69
69
|
// ─────────────────────────────────────────────────────────────
|
|
70
|
-
// captureElement
|
|
70
|
+
// captureElement - single-pass atomic extraction of all v2 metrics.
|
|
71
71
|
//
|
|
72
72
|
// All data collection (core props, ancestry, visibility, anchors,
|
|
73
73
|
// shadow-DOM flag) runs inside ONE page.evaluate() call so the
|
|
74
74
|
// browser serialises a single IPC payload. Returns null on any
|
|
75
|
-
// failure
|
|
75
|
+
// failure - callers must treat null as "capture skipped" and not
|
|
76
76
|
// block the heal.
|
|
77
77
|
// ─────────────────────────────────────────────────────────────
|
|
78
78
|
async captureElement(page, selector, framePath = []) {
|
|
@@ -219,7 +219,7 @@ class SnapshotService {
|
|
|
219
219
|
closestLabel = t;
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
// P4: implicit <label> wrapper
|
|
222
|
+
// P4: implicit <label> wrapper - clone and strip the form element
|
|
223
223
|
if (!closestLabel) {
|
|
224
224
|
const wrapper = el.closest("label");
|
|
225
225
|
if (wrapper) {
|
|
@@ -318,10 +318,10 @@ class SnapshotService {
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
// ─────────────────────────────────────────────────────────────
|
|
321
|
-
// migrateToV2
|
|
321
|
+
// migrateToV2 - backfills default v2 fields onto a v1 snapshot.
|
|
322
322
|
//
|
|
323
323
|
// ancestry, visibility, and anchors are empty / safe defaults.
|
|
324
|
-
// The migrated snapshot is NOT re-saved to disk automatically
|
|
324
|
+
// The migrated snapshot is NOT re-saved to disk automatically -
|
|
325
325
|
// the engine decides when to flush.
|
|
326
326
|
// ─────────────────────────────────────────────────────────────
|
|
327
327
|
migrateToV2(v1) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ASTUpdateResult } from
|
|
2
|
-
import type { DnaRecord, DnaEditPatch } from
|
|
1
|
+
import { type ASTUpdateResult } from "./ASTSourceUpdater.js";
|
|
2
|
+
import type { DnaRecord, DnaEditPatch } from "./types.js";
|
|
3
3
|
interface CallerContext {
|
|
4
4
|
filePath: string;
|
|
5
5
|
line: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourceLinkService.d.ts","sourceRoot":"","sources":["../../src/services/SourceLinkService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SourceLinkService.d.ts","sourceRoot":"","sources":["../../src/services/SourceLinkService.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1D,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAoDD,MAAM,WAAW,kBAAkB;IACjC,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IACvD,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IAC9E,iBAAiB,CACf,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7B;AAED,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGrB;IAEH,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa;IAItD,uBAAuB,CACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,kBAAkB,EAAE;IAyDjB,iBAAiB,CACrB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,CAAC;CAuE5B"}
|
|
@@ -10,15 +10,15 @@ const ts_morph_1 = require("ts-morph");
|
|
|
10
10
|
const ASTSourceUpdater_js_1 = require("./ASTSourceUpdater.js");
|
|
11
11
|
// Mirror of ASTSourceUpdater's module-scoped ALL_SELECTOR_METHODS.
|
|
12
12
|
const ALL_SELECTOR_METHODS = new Set([
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
"locator",
|
|
14
|
+
"frameLocator",
|
|
15
|
+
"getByRole",
|
|
16
|
+
"getByLabel",
|
|
17
|
+
"getByText",
|
|
18
|
+
"getByTestId",
|
|
19
|
+
"getByPlaceholder",
|
|
20
|
+
"getByAltText",
|
|
21
|
+
"getByTitle",
|
|
22
22
|
]);
|
|
23
23
|
function dnaError(message, code) {
|
|
24
24
|
const err = new Error(message);
|
|
@@ -33,11 +33,12 @@ function extractSelector(call) {
|
|
|
33
33
|
return null;
|
|
34
34
|
const first = args[0];
|
|
35
35
|
const kind = first.getKind();
|
|
36
|
-
if (kind === ts_morph_1.SyntaxKind.StringLiteral ||
|
|
36
|
+
if (kind === ts_morph_1.SyntaxKind.StringLiteral ||
|
|
37
|
+
kind === ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
37
38
|
const raw = first.getText();
|
|
38
39
|
return raw.slice(1, -1); // strip surrounding quotes/backticks
|
|
39
40
|
}
|
|
40
|
-
// For template literals with expressions, identifiers, etc.
|
|
41
|
+
// For template literals with expressions, identifiers, etc. - return raw text.
|
|
41
42
|
return first.getText();
|
|
42
43
|
}
|
|
43
44
|
// Get the method name from a CallExpression's callee.
|
|
@@ -68,7 +69,7 @@ class SourceLinkService {
|
|
|
68
69
|
return [];
|
|
69
70
|
let sf;
|
|
70
71
|
try {
|
|
71
|
-
// addSourceFileAtPath throws if already added
|
|
72
|
+
// addSourceFileAtPath throws if already added - use getOrAdd pattern.
|
|
72
73
|
sf =
|
|
73
74
|
this.project.getSourceFile(absPath) ??
|
|
74
75
|
this.project.addSourceFileAtPath(absPath);
|
|
@@ -91,7 +92,7 @@ class SourceLinkService {
|
|
|
91
92
|
if (selector === null)
|
|
92
93
|
return;
|
|
93
94
|
const raw = call.getText();
|
|
94
|
-
const preview = raw.length > 80 ? raw.slice(0, 79) +
|
|
95
|
+
const preview = raw.length > 80 ? raw.slice(0, 79) + "…" : raw;
|
|
95
96
|
candidates.push({
|
|
96
97
|
col: call.getStart(),
|
|
97
98
|
preview,
|
|
@@ -110,16 +111,24 @@ class SourceLinkService {
|
|
|
110
111
|
}
|
|
111
112
|
async applySourceUpdate(record, patch) {
|
|
112
113
|
if (!record.sourceFile) {
|
|
113
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
reason: "DNA record has no sourceFile",
|
|
117
|
+
strategy: "none",
|
|
118
|
+
};
|
|
114
119
|
}
|
|
115
120
|
if (!(0, fs_1.existsSync)(path_1.default.resolve(record.sourceFile))) {
|
|
116
|
-
throw dnaError(`Source file not found: ${record.sourceFile}`,
|
|
121
|
+
throw dnaError(`Source file not found: ${record.sourceFile}`, "SOURCE_FILE_MISSING");
|
|
117
122
|
}
|
|
118
123
|
if (record.sourceLine <= 0) {
|
|
119
|
-
return {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
reason: "DNA record has no valid sourceLine",
|
|
127
|
+
strategy: "none",
|
|
128
|
+
};
|
|
120
129
|
}
|
|
121
130
|
const caller = this.resolveCallerContext(record);
|
|
122
|
-
if (patch.field ===
|
|
131
|
+
if (patch.field === "selector") {
|
|
123
132
|
const newSelector = patch.newValue;
|
|
124
133
|
// Resolve oldSelector: prefer patch.oldValue, fall back to the locator
|
|
125
134
|
// at patch.locatorIndex on the recorded line.
|
|
@@ -131,25 +140,25 @@ class SourceLinkService {
|
|
|
131
140
|
return {
|
|
132
141
|
success: false,
|
|
133
142
|
reason: `No locator found at index ${patch.locatorIndex} on line ${record.sourceLine}`,
|
|
134
|
-
strategy:
|
|
143
|
+
strategy: "none",
|
|
135
144
|
};
|
|
136
145
|
}
|
|
137
146
|
oldSelector = target.selector;
|
|
138
147
|
}
|
|
139
148
|
return this.updater.update(caller, oldSelector, newSelector);
|
|
140
149
|
}
|
|
141
|
-
if (patch.field ===
|
|
150
|
+
if (patch.field === "text") {
|
|
142
151
|
// Use ASTSourceUpdater's contentChange path for text-content edits.
|
|
143
152
|
const contentChange = {
|
|
144
|
-
oldText: String(patch.oldValue ??
|
|
145
|
-
newText: String(patch.newValue ??
|
|
153
|
+
oldText: String(patch.oldValue ?? ""),
|
|
154
|
+
newText: String(patch.newValue ?? ""),
|
|
146
155
|
};
|
|
147
156
|
return this.updater.update(caller, record.selector, record.selector, undefined, undefined, contentChange);
|
|
148
157
|
}
|
|
149
158
|
return {
|
|
150
159
|
success: false,
|
|
151
160
|
reason: `Source update not supported for field: ${patch.field}`,
|
|
152
|
-
strategy:
|
|
161
|
+
strategy: "none",
|
|
153
162
|
};
|
|
154
163
|
}
|
|
155
164
|
}
|
|
@@ -4,7 +4,7 @@ interface UpdateResult {
|
|
|
4
4
|
success: boolean;
|
|
5
5
|
reason: string;
|
|
6
6
|
lineUpdated?: number;
|
|
7
|
-
/** Canonical locator string written to disk
|
|
7
|
+
/** Canonical locator string written to disk - use this for runtime execution. */
|
|
8
8
|
healedLocatorString?: string;
|
|
9
9
|
/** Populated by semantic dispatch: where the definition was mutated. */
|
|
10
10
|
definitionSite?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/SourceUpdater.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA4BtD,UAAU,YAAY;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAqZD,qBAAa,aAAa;IAMxB,MAAM,CAAC,MAAM,CACX,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,UAAU,CAAC,EAAE,eAAe,EAAE,EAC9B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EACxC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EACvC,cAAc,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,QAAQ,EACrD,UAAU,CAAC,EAAE,OAAO,GACnB,YAAY;IAoDf,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAwC/B,OAAO,CAAC,MAAM,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"SourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/SourceUpdater.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA4BtD,UAAU,YAAY;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAqZD,qBAAa,aAAa;IAMxB,MAAM,CAAC,MAAM,CACX,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,UAAU,CAAC,EAAE,eAAe,EAAE,EAC9B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EACxC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EACvC,cAAc,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,QAAQ,EACrD,UAAU,CAAC,EAAE,OAAO,GACnB,YAAY;IAoDf,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAwC/B,OAAO,CAAC,MAAM,CAAC,WAAW;IAkT1B,OAAO,CAAC,MAAM,CAAC,2BAA2B;IA8E1C,OAAO,CAAC,MAAM,CAAC,wBAAwB;IA8EvC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAoErC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAsBjC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA6DpC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAuCpC,OAAO,CAAC,MAAM,CAAC,wBAAwB;IA6CvC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAqCrC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAqGrC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IA0BjC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAatC,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAgBlD,MAAM,CAAC,cAAc,CACnB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,YAAY;IA0Cf,0FAA0F;IAC1F,MAAM,CAAC,eAAe,IAAI,IAAI;CAG/B"}
|