sela-core 1.0.7 → 1.0.9
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 +27 -17
- 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/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- 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 +12 -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 +13 -17
- 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 +117 -51
- package/dist/errors/SelaError.d.ts +8 -1
- package/dist/errors/SelaError.d.ts.map +1 -1
- package/dist/errors/SelaError.js +14 -7
- package/dist/fixtures/expectProxy.d.ts +2 -2
- package/dist/fixtures/expectProxy.js +16 -16
- package/dist/fixtures/index.d.ts +18 -2
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +47 -27
- package/dist/fixtures/moduleExpect.js +7 -7
- package/dist/fixtures/proxyTag.d.ts +1 -1
- package/dist/fixtures/proxyTag.js +4 -4
- 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 +48 -3
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +283 -105
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- 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/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- 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 +31 -55
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +864 -1240
- package/dist/services/HealingAdvisory.d.ts +9 -1
- package/dist/services/HealingAdvisory.d.ts.map +1 -1
- package/dist/services/HealingAdvisory.js +9 -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.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +12 -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 +34 -124
- package/dist/services/LLMService.d.ts +1 -1
- package/dist/services/LLMService.js +16 -16
- package/dist/services/MutationApplier.d.ts +55 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +322 -0
- 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 +9 -2
- package/dist/services/PendingPromptLedger.d.ts.map +1 -1
- package/dist/services/PendingPromptLedger.js +31 -6
- package/dist/services/ReportGenerator.d.ts +116 -30
- package/dist/services/ReportGenerator.d.ts.map +1 -1
- package/dist/services/ReportGenerator.js +149 -62
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +4 -7
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +29 -106
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.js +6 -6
- 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 +44 -26
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- 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/DOMUtils.d.ts +18 -2
- package/dist/utils/DOMUtils.d.ts.map +1 -1
- package/dist/utils/DOMUtils.js +335 -49
- 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
|
@@ -4,7 +4,15 @@ export declare enum AdvisoryCode {
|
|
|
4
4
|
CROSS_FILE_MUTATION = "CROSS_FILE_MUTATION",
|
|
5
5
|
MANUAL_REVIEW_TEMPLATE_COMPLEX = "MANUAL_REVIEW_TEMPLATE_COMPLEX",
|
|
6
6
|
MANUAL_REVIEW_CONDITIONAL_RETURN = "MANUAL_REVIEW_CONDITIONAL_RETURN",
|
|
7
|
-
POST_MUTATION_TYPE_ERROR = "POST_MUTATION_TYPE_ERROR"
|
|
7
|
+
POST_MUTATION_TYPE_ERROR = "POST_MUTATION_TYPE_ERROR",
|
|
8
|
+
/**
|
|
9
|
+
* A definition-site string constant was healed to a valid PUBLIC selector
|
|
10
|
+
* string (e.g. `role=button[name="…"]`) because the constant is consumed by
|
|
11
|
+
* `.locator(VAR)` and cannot hold a semantic locator. The AI's preferred
|
|
12
|
+
* semantic method form (e.g. `getByRole(...)`) is suggested as a cleaner,
|
|
13
|
+
* optional refactor.
|
|
14
|
+
*/
|
|
15
|
+
SEMANTIC_UPGRADE_SUGGESTED = "SEMANTIC_UPGRADE_SUGGESTED"
|
|
8
16
|
}
|
|
9
17
|
export interface Advisory {
|
|
10
18
|
code: AdvisoryCode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealingAdvisory.d.ts","sourceRoot":"","sources":["../../src/services/HealingAdvisory.ts"],"names":[],"mappings":"AAQA,oBAAY,YAAY;IACtB,oBAAoB,yBAAyB;IAC7C,oBAAoB,yBAAyB;IAC7C,mBAAmB,wBAAwB;IAC3C,8BAA8B,mCAAmC;IACjE,gCAAgC,qCAAqC;IACrE,wBAAwB,6BAA6B;
|
|
1
|
+
{"version":3,"file":"HealingAdvisory.d.ts","sourceRoot":"","sources":["../../src/services/HealingAdvisory.ts"],"names":[],"mappings":"AAQA,oBAAY,YAAY;IACtB,oBAAoB,yBAAyB;IAC7C,oBAAoB,yBAAyB;IAC7C,mBAAmB,wBAAwB;IAC3C,8BAA8B,mCAAmC;IACjE,gCAAgC,qCAAqC;IACrE,wBAAwB,6BAA6B;IACrD;;;;;;OAMG;IACH,0BAA0B,+BAA+B;CAC1D;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAkB;IAE/B,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;IAab,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Typed advisory system for definition-site mutations.
|
|
6
6
|
// Advisories are buffered during the AST phase and flushed AFTER
|
|
7
|
-
// Playwright prints its test result
|
|
7
|
+
// Playwright prints its test result - prevents interleaving.
|
|
8
8
|
// ═══════════════════════════════════════════════════════════════════
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.AdvisoryBuffer = exports.AdvisoryCode = void 0;
|
|
@@ -16,6 +16,14 @@ var AdvisoryCode;
|
|
|
16
16
|
AdvisoryCode["MANUAL_REVIEW_TEMPLATE_COMPLEX"] = "MANUAL_REVIEW_TEMPLATE_COMPLEX";
|
|
17
17
|
AdvisoryCode["MANUAL_REVIEW_CONDITIONAL_RETURN"] = "MANUAL_REVIEW_CONDITIONAL_RETURN";
|
|
18
18
|
AdvisoryCode["POST_MUTATION_TYPE_ERROR"] = "POST_MUTATION_TYPE_ERROR";
|
|
19
|
+
/**
|
|
20
|
+
* A definition-site string constant was healed to a valid PUBLIC selector
|
|
21
|
+
* string (e.g. `role=button[name="…"]`) because the constant is consumed by
|
|
22
|
+
* `.locator(VAR)` and cannot hold a semantic locator. The AI's preferred
|
|
23
|
+
* semantic method form (e.g. `getByRole(...)`) is suggested as a cleaner,
|
|
24
|
+
* optional refactor.
|
|
25
|
+
*/
|
|
26
|
+
AdvisoryCode["SEMANTIC_UPGRADE_SUGGESTED"] = "SEMANTIC_UPGRADE_SUGGESTED";
|
|
19
27
|
})(AdvisoryCode || (exports.AdvisoryCode = AdvisoryCode = {}));
|
|
20
28
|
class AdvisoryBuffer {
|
|
21
29
|
items = [];
|
|
@@ -3,11 +3,11 @@ export type HealCacheMode = "action" | "assertion";
|
|
|
3
3
|
export interface CacheKeyInput {
|
|
4
4
|
/** Absolute or project-relative spec path containing the failing locator. */
|
|
5
5
|
filePath: string;
|
|
6
|
-
/** 1-based line of the failing call site
|
|
6
|
+
/** 1-based line of the failing call site - paired with filePath as a fingerprint. */
|
|
7
7
|
line: number;
|
|
8
8
|
/**
|
|
9
9
|
* Stable content hash of the DNA snapshot used to drive the heal. When the
|
|
10
|
-
* DNA rewrites, this changes
|
|
10
|
+
* DNA rewrites, this changes - every cached entry for the old DNA
|
|
11
11
|
* silently stops matching and ages out via TTL.
|
|
12
12
|
*/
|
|
13
13
|
dnaContentHash: string;
|
|
@@ -66,7 +66,7 @@ export interface HealingCacheSetInput extends CacheKeyInput {
|
|
|
66
66
|
}
|
|
67
67
|
export declare function computeCacheKey(input: CacheKeyInput): string;
|
|
68
68
|
/**
|
|
69
|
-
* Convenience helper
|
|
69
|
+
* Convenience helper - fingerprint the JSON form of a DNA snapshot so
|
|
70
70
|
* the cache key changes whenever the DNA is rewritten.
|
|
71
71
|
*/
|
|
72
72
|
export declare function hashDnaContent(dna: unknown): string;
|
|
@@ -83,7 +83,7 @@ export declare class HealingCacheService {
|
|
|
83
83
|
});
|
|
84
84
|
/**
|
|
85
85
|
* Read-only lookup. Bumps `hitCount` / `lastHitAt` IN-MEMORY only on
|
|
86
|
-
* the returned entry reference
|
|
86
|
+
* the returned entry reference - never persisted on the get() path.
|
|
87
87
|
* This eliminates the read-write race where parallel workers each
|
|
88
88
|
* call get() and serialise full entries-map snapshots back to disk,
|
|
89
89
|
* overwriting peer set() operations.
|
|
@@ -96,7 +96,7 @@ export declare class HealingCacheService {
|
|
|
96
96
|
* deadlocks downstream callers.
|
|
97
97
|
*/
|
|
98
98
|
set(input: HealingCacheSetInput): Promise<void>;
|
|
99
|
-
/** Synchronous variant
|
|
99
|
+
/** Synchronous variant - used by teardown / CLI paths. */
|
|
100
100
|
setSync(input: HealingCacheSetInput): void;
|
|
101
101
|
/** Wipe every entry. Used by tests and the (forthcoming) `sela cache clear` CLI. */
|
|
102
102
|
clear(): void;
|
|
@@ -105,7 +105,7 @@ export declare class HealingCacheService {
|
|
|
105
105
|
/** Absolute path of the on-disk cache file. */
|
|
106
106
|
filePath(): string;
|
|
107
107
|
/**
|
|
108
|
-
* Prunes expired entries. Cheap O(n) scan
|
|
108
|
+
* Prunes expired entries. Cheap O(n) scan - intended for engine
|
|
109
109
|
* startup. Returns the number of entries removed.
|
|
110
110
|
*/
|
|
111
111
|
pruneExpired(): number;
|
|
@@ -115,13 +115,13 @@ export declare class HealingCacheService {
|
|
|
115
115
|
/**
|
|
116
116
|
* Reads + parses the cache file. Tolerates every realistic failure
|
|
117
117
|
* (missing, empty, malformed, wrong schema version) by returning a
|
|
118
|
-
* fresh empty shell
|
|
118
|
+
* fresh empty shell - the contract is "cache misses are silent".
|
|
119
119
|
*/
|
|
120
120
|
private safeRead;
|
|
121
121
|
/**
|
|
122
122
|
* Atomic write: write to `<file>.tmp`, copy current to `<file>.bak`,
|
|
123
123
|
* then rename `<file>.tmp` → `<file>`. The rename is the single
|
|
124
|
-
* commit point
|
|
124
|
+
* commit point - a crash between bak and rename leaves the bak file
|
|
125
125
|
* intact for future recovery.
|
|
126
126
|
*/
|
|
127
127
|
private atomicWrite;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealingCacheService.d.ts","sourceRoot":"","sources":["../../src/services/HealingCacheService.ts"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAMvE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,SAAS;IACjB,aAAa,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAc5D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"HealingCacheService.d.ts","sourceRoot":"","sources":["../../src/services/HealingCacheService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/services/HealingCacheService.ts
|
|
3
3
|
//
|
|
4
|
-
// Local healing cache
|
|
4
|
+
// Local healing cache - short-circuits the LLM round-trip when an
|
|
5
5
|
// identical heal-context has been resolved successfully in a prior
|
|
6
6
|
// session.
|
|
7
7
|
//
|
|
8
8
|
// Cache key (matches DX MVP Phase 6 plan):
|
|
9
9
|
// SHA1(filePath + ":" + line + "::" + dnaContentHash + "::" + brokenSelector + "::" + mode)
|
|
10
10
|
// Including `dnaContentHash` means a DNA rewrite automatically
|
|
11
|
-
// invalidates every entry keyed against that fingerprint
|
|
11
|
+
// invalidates every entry keyed against that fingerprint - old
|
|
12
12
|
// entries simply stop matching and age out via TTL.
|
|
13
13
|
//
|
|
14
14
|
// Cache value:
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// callers.
|
|
32
32
|
//
|
|
33
33
|
// 2. Cross-process file lock
|
|
34
|
-
// Playwright workers run as separate processes
|
|
34
|
+
// Playwright workers run as separate processes - atomic
|
|
35
35
|
// `mkdir <cacheDir>/.sela-cache.lock` is used as the mutex. Second
|
|
36
36
|
// process gets `EEXIST`, polls with exponential backoff
|
|
37
37
|
// (50ms → 800ms, max 6 retries ≈ 3.15 s ceiling). Lock is held
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
//
|
|
40
40
|
// 3. Atomic write via temp + rename
|
|
41
41
|
// Write to `.sela-cache.json.tmp`, copy the previous file to
|
|
42
|
-
// `.sela-cache.json.bak`, then `fs.renameSync(tmp, target)`
|
|
42
|
+
// `.sela-cache.json.bak`, then `fs.renameSync(tmp, target)` -
|
|
43
43
|
// atomic on POSIX and on Windows (Node ≥14) when source and
|
|
44
44
|
// target share a volume (always true here).
|
|
45
45
|
//
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
// acquires the LLM via the normal path; cache writes happen post-success.
|
|
49
49
|
//
|
|
50
50
|
// `get()` is strictly read-only on disk: hit-count / lastHitAt are
|
|
51
|
-
// bumped IN-MEMORY only
|
|
51
|
+
// bumped IN-MEMORY only - they never trigger a disk write. This avoids
|
|
52
52
|
// the documented bug where concurrent worker `get()` calls would
|
|
53
53
|
// overwrite valid `set()` operations from peer workers.
|
|
54
54
|
//
|
|
55
55
|
// Failure tolerance: any read failure (missing, malformed JSON, wrong
|
|
56
56
|
// schema, EACCES, …) returns "no entries" instead of throwing. The
|
|
57
|
-
// cache is a latency optimisation
|
|
57
|
+
// cache is a latency optimisation - never a hard dependency of the
|
|
58
58
|
// heal pipeline.
|
|
59
59
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
60
60
|
if (k2 === undefined) k2 = k;
|
|
@@ -126,7 +126,7 @@ function computeCacheKey(input) {
|
|
|
126
126
|
.digest("hex");
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
|
-
* Convenience helper
|
|
129
|
+
* Convenience helper - fingerprint the JSON form of a DNA snapshot so
|
|
130
130
|
* the cache key changes whenever the DNA is rewritten.
|
|
131
131
|
*/
|
|
132
132
|
function hashDnaContent(dna) {
|
|
@@ -148,7 +148,7 @@ class HealingCacheService {
|
|
|
148
148
|
tmpSuffix = ".tmp";
|
|
149
149
|
bakSuffix = ".bak";
|
|
150
150
|
// In-process serialisation. Every new set() chains onto the prior
|
|
151
|
-
// tail Promise. The chain is fault-tolerant
|
|
151
|
+
// tail Promise. The chain is fault-tolerant - see `set()` below.
|
|
152
152
|
inFlight = Promise.resolve();
|
|
153
153
|
constructor(opts) {
|
|
154
154
|
this.cacheDir = opts?.cacheDir ?? process.cwd();
|
|
@@ -160,7 +160,7 @@ class HealingCacheService {
|
|
|
160
160
|
// ─────────────────────────────────────────────────────────────
|
|
161
161
|
/**
|
|
162
162
|
* Read-only lookup. Bumps `hitCount` / `lastHitAt` IN-MEMORY only on
|
|
163
|
-
* the returned entry reference
|
|
163
|
+
* the returned entry reference - never persisted on the get() path.
|
|
164
164
|
* This eliminates the read-write race where parallel workers each
|
|
165
165
|
* call get() and serialise full entries-map snapshots back to disk,
|
|
166
166
|
* overwriting peer set() operations.
|
|
@@ -175,7 +175,7 @@ class HealingCacheService {
|
|
|
175
175
|
return null;
|
|
176
176
|
if (this.isExpired(entry))
|
|
177
177
|
return null;
|
|
178
|
-
// In-memory only
|
|
178
|
+
// In-memory only - DO NOT write back. Hit-count drift across workers
|
|
179
179
|
// is acceptable; corrupting peer writes is not.
|
|
180
180
|
entry.hitCount = (entry.hitCount ?? 0) + 1;
|
|
181
181
|
entry.lastHitAt = new Date().toISOString();
|
|
@@ -195,11 +195,11 @@ class HealingCacheService {
|
|
|
195
195
|
// Chain onto the previous tail, but swallow rejections via a
|
|
196
196
|
// `.catch(noop)` adapter. Without this, an earlier rejected set()
|
|
197
197
|
// would propagate through every subsequent `await prev` and either
|
|
198
|
-
// throw at the wrong caller or
|
|
198
|
+
// throw at the wrong caller or - worse - leak through the finally
|
|
199
199
|
// block without releasing the queue. The wrapper guarantees the
|
|
200
200
|
// queue is FIFO and rejection-safe.
|
|
201
201
|
const prev = this.inFlight.catch(() => {
|
|
202
|
-
/* swallow
|
|
202
|
+
/* swallow - upstream failure handled by its own caller */
|
|
203
203
|
});
|
|
204
204
|
let release;
|
|
205
205
|
const myTurn = new Promise((res) => {
|
|
@@ -211,12 +211,12 @@ class HealingCacheService {
|
|
|
211
211
|
await this.lockedWrite(input);
|
|
212
212
|
}
|
|
213
213
|
finally {
|
|
214
|
-
// Always release
|
|
214
|
+
// Always release - even on throw - so the next queued caller
|
|
215
215
|
// unblocks. Errors still propagate up to *this* caller normally.
|
|
216
216
|
release();
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
/** Synchronous variant
|
|
219
|
+
/** Synchronous variant - used by teardown / CLI paths. */
|
|
220
220
|
setSync(input) {
|
|
221
221
|
if (DryRunGuard_1.DryRunGuard.active())
|
|
222
222
|
return;
|
|
@@ -253,7 +253,7 @@ class HealingCacheService {
|
|
|
253
253
|
return path.join(this.cacheDir, this.filename);
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
|
-
* Prunes expired entries. Cheap O(n) scan
|
|
256
|
+
* Prunes expired entries. Cheap O(n) scan - intended for engine
|
|
257
257
|
* startup. Returns the number of entries removed.
|
|
258
258
|
*/
|
|
259
259
|
pruneExpired() {
|
|
@@ -272,7 +272,7 @@ class HealingCacheService {
|
|
|
272
272
|
return removed;
|
|
273
273
|
}
|
|
274
274
|
// ─────────────────────────────────────────────────────────────
|
|
275
|
-
// INTERNAL
|
|
275
|
+
// INTERNAL - read / write / lock
|
|
276
276
|
// ─────────────────────────────────────────────────────────────
|
|
277
277
|
isExpired(entry) {
|
|
278
278
|
if (!entry.ttlExpiresAt)
|
|
@@ -285,7 +285,7 @@ class HealingCacheService {
|
|
|
285
285
|
async lockedWrite(input) {
|
|
286
286
|
const acquired = await this.acquireLockWithBackoff();
|
|
287
287
|
if (!acquired) {
|
|
288
|
-
console.warn(`[HealingCache] ⚠️ Could not acquire lock at ${this.lockDir}
|
|
288
|
+
console.warn(`[HealingCache] ⚠️ Could not acquire lock at ${this.lockDir} - write skipped`);
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
291
|
try {
|
|
@@ -326,7 +326,7 @@ class HealingCacheService {
|
|
|
326
326
|
/**
|
|
327
327
|
* Reads + parses the cache file. Tolerates every realistic failure
|
|
328
328
|
* (missing, empty, malformed, wrong schema version) by returning a
|
|
329
|
-
* fresh empty shell
|
|
329
|
+
* fresh empty shell - the contract is "cache misses are silent".
|
|
330
330
|
*/
|
|
331
331
|
safeRead() {
|
|
332
332
|
const target = path.join(this.cacheDir, this.filename);
|
|
@@ -345,7 +345,7 @@ class HealingCacheService {
|
|
|
345
345
|
parsed = JSON.parse(raw);
|
|
346
346
|
}
|
|
347
347
|
catch (err) {
|
|
348
|
-
console.warn(`[HealingCache] ⚠️ Corrupt cache file at ${target}
|
|
348
|
+
console.warn(`[HealingCache] ⚠️ Corrupt cache file at ${target} - starting fresh (${err.message})`);
|
|
349
349
|
return { schemaVersion: 1, entries: {} };
|
|
350
350
|
}
|
|
351
351
|
if (!parsed ||
|
|
@@ -355,7 +355,7 @@ class HealingCacheService {
|
|
|
355
355
|
typeof parsed.entries !== "object" ||
|
|
356
356
|
parsed.entries === null ||
|
|
357
357
|
Array.isArray(parsed.entries)) {
|
|
358
|
-
console.warn(`[HealingCache] ⚠️ Unknown cache schema at ${target}
|
|
358
|
+
console.warn(`[HealingCache] ⚠️ Unknown cache schema at ${target} - starting fresh`);
|
|
359
359
|
return { schemaVersion: 1, entries: {} };
|
|
360
360
|
}
|
|
361
361
|
return parsed;
|
|
@@ -363,7 +363,7 @@ class HealingCacheService {
|
|
|
363
363
|
/**
|
|
364
364
|
* Atomic write: write to `<file>.tmp`, copy current to `<file>.bak`,
|
|
365
365
|
* then rename `<file>.tmp` → `<file>`. The rename is the single
|
|
366
|
-
* commit point
|
|
366
|
+
* commit point - a crash between bak and rename leaves the bak file
|
|
367
367
|
* intact for future recovery.
|
|
368
368
|
*/
|
|
369
369
|
atomicWrite(file) {
|
|
@@ -386,7 +386,7 @@ class HealingCacheService {
|
|
|
386
386
|
fs.renameSync(tmp, target);
|
|
387
387
|
}
|
|
388
388
|
// ─────────────────────────────────────────────────────────────
|
|
389
|
-
// FILE LOCK
|
|
389
|
+
// FILE LOCK - atomic mkdir as a cross-process mutex
|
|
390
390
|
// ─────────────────────────────────────────────────────────────
|
|
391
391
|
async acquireLockWithBackoff() {
|
|
392
392
|
const delaysMs = [50, 100, 200, 400, 800, 800];
|
|
@@ -430,7 +430,7 @@ class HealingCacheService {
|
|
|
430
430
|
break;
|
|
431
431
|
sleepBusy(delaysMs[attempt]);
|
|
432
432
|
}
|
|
433
|
-
console.warn(`[HealingCache] ⚠️ Lock acquire failed (sync)
|
|
433
|
+
console.warn(`[HealingCache] ⚠️ Lock acquire failed (sync) - operation skipped`);
|
|
434
434
|
}
|
|
435
435
|
releaseLock() {
|
|
436
436
|
try {
|
|
@@ -455,6 +455,6 @@ function sleepBusy(ms) {
|
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
// ═══════════════════════════════════════════════════════════════════
|
|
458
|
-
// Module-level singleton
|
|
458
|
+
// Module-level singleton - engines reuse the same cache file by default
|
|
459
459
|
// ═══════════════════════════════════════════════════════════════════
|
|
460
460
|
exports.sharedHealingCache = new HealingCacheService();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealthReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealthReportService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAa,YAAY,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,YAAY,CAAC;IACxC,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC;CACtC;
|
|
1
|
+
{"version":3,"file":"HealthReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealthReportService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAa,YAAY,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,YAAY,CAAC;IACxC,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC;CACtC;AAoBD,qBAAa,mBAAoB,YAAW,oBAAoB;IAC9D,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,YAAY;IAkBvC,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM;CA0FrC"}
|
|
@@ -28,48 +28,48 @@ class HealthReportService {
|
|
|
28
28
|
snapshotDir,
|
|
29
29
|
total: records.length,
|
|
30
30
|
orphaned: records.filter((r) => r.isOrphaned),
|
|
31
|
-
unverifiedHeals: records.filter((r) => !r.isOrphaned && r.status ===
|
|
31
|
+
unverifiedHeals: records.filter((r) => !r.isOrphaned && r.status === "healed"),
|
|
32
32
|
recentlyHealed: records.filter((r) => r.lastHealed !== null && isWithin7Days(r.lastHealed)),
|
|
33
33
|
neverHealed: records.filter((r) => r.lastHealed === null),
|
|
34
34
|
topHealedFiles: topHealedFiles(records),
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
format(report) {
|
|
38
|
-
const lines = [
|
|
39
|
-
lines.push(chalk_1.default.bold.cyan(
|
|
38
|
+
const lines = [""];
|
|
39
|
+
lines.push(chalk_1.default.bold.cyan(" Sela Health Report") +
|
|
40
40
|
chalk_1.default.dim(` (${new Date(report.generatedAt).toISOString()})`));
|
|
41
|
-
lines.push(
|
|
41
|
+
lines.push("");
|
|
42
42
|
const row = (label, value) => ` ${chalk_1.default.dim(label.padEnd(20))} ${value}`;
|
|
43
|
-
lines.push(row(
|
|
43
|
+
lines.push(row("Total DNAs", chalk_1.default.bold(String(report.total))));
|
|
44
44
|
const orphanCount = report.orphaned.length;
|
|
45
|
-
lines.push(row(
|
|
46
|
-
? chalk_1.default.yellow(`${orphanCount} ⚠`)
|
|
47
|
-
: chalk_1.default.green('0')));
|
|
45
|
+
lines.push(row("Orphaned", orphanCount > 0 ? chalk_1.default.yellow(`${orphanCount} ⚠`) : chalk_1.default.green("0")));
|
|
48
46
|
const unverifiedCount = report.unverifiedHeals.length;
|
|
49
|
-
lines.push(row(
|
|
50
|
-
? chalk_1.default.yellow(`${unverifiedCount} ⚠ ${chalk_1.default.dim(
|
|
51
|
-
: chalk_1.default.green(
|
|
47
|
+
lines.push(row("Unverified heals", unverifiedCount > 0
|
|
48
|
+
? chalk_1.default.yellow(`${unverifiedCount} ⚠ ${chalk_1.default.dim("(AI-healed, needs review)")}`)
|
|
49
|
+
: chalk_1.default.green("0")));
|
|
52
50
|
const brokenTotal = orphanCount + unverifiedCount;
|
|
53
51
|
const brokenRatio = report.total > 0
|
|
54
52
|
? ((brokenTotal / report.total) * 100).toFixed(1)
|
|
55
|
-
:
|
|
56
|
-
lines.push(row(
|
|
57
|
-
lines.push(row(
|
|
58
|
-
lines.push(row(
|
|
59
|
-
lines.push(row(
|
|
53
|
+
: "0.0";
|
|
54
|
+
lines.push(row("Broken ratio", (brokenTotal > 0 ? chalk_1.default.yellow : chalk_1.default.green)(`${brokenRatio}% ${chalk_1.default.dim(`(${brokenTotal}/${report.total})`)}`)));
|
|
55
|
+
lines.push(row("Healed (7 days)", chalk_1.default.bold(String(report.recentlyHealed.length))));
|
|
56
|
+
lines.push(row("Healed (all time)", chalk_1.default.bold(String(report.total - report.neverHealed.length))));
|
|
57
|
+
lines.push(row("Never healed", chalk_1.default.bold(String(report.neverHealed.length))));
|
|
60
58
|
if (report.topHealedFiles.length > 0) {
|
|
61
|
-
lines.push(
|
|
62
|
-
lines.push(` ${chalk_1.default.bold(
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(` ${chalk_1.default.bold("Top healed files:")}`);
|
|
63
61
|
for (const { sourceFile, count } of report.topHealedFiles.slice(0, 5)) {
|
|
64
62
|
const rel = path_1.default.relative(process.cwd(), sourceFile);
|
|
65
|
-
const label = (rel.length < sourceFile.length ? rel : sourceFile)
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const label = (rel.length < sourceFile.length ? rel : sourceFile)
|
|
64
|
+
.slice(-60)
|
|
65
|
+
.padEnd(62);
|
|
66
|
+
const noun = count === 1 ? "heal" : "heals";
|
|
67
|
+
lines.push(` ${chalk_1.default.cyan(label)} ${chalk_1.default.dim("-")} ${chalk_1.default.bold(String(count))} ${chalk_1.default.dim(noun)}`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
if (report.orphaned.length > 0) {
|
|
71
|
-
lines.push(
|
|
72
|
-
lines.push(` ${chalk_1.default.yellow.bold(
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(` ${chalk_1.default.yellow.bold("Orphaned DNA files:")}`);
|
|
73
73
|
for (const r of report.orphaned.slice(0, 10)) {
|
|
74
74
|
lines.push(` ${chalk_1.default.dim(r.key)} ${chalk_1.default.yellow(r.sourceFile)}`);
|
|
75
75
|
}
|
|
@@ -77,8 +77,8 @@ class HealthReportService {
|
|
|
77
77
|
lines.push(chalk_1.default.dim(` … and ${report.orphaned.length - 10} more`));
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
lines.push(
|
|
81
|
-
return lines.join(
|
|
80
|
+
lines.push("");
|
|
81
|
+
return lines.join("\n");
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
exports.HealthReportService = HealthReportService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InitializerUpdater.d.ts","sourceRoot":"","sources":["../../src/services/InitializerUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"InitializerUpdater.d.ts","sourceRoot":"","sources":["../../src/services/InitializerUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAC;AAe3D,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,kBAAkB;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CACV,SAAS,EAAE,IAAI,EACf,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,iBAAiB,GAAG,IAAI;CAkD5B"}
|
|
@@ -5,6 +5,7 @@ const ts_morph_1 = require("ts-morph");
|
|
|
5
5
|
const TemplateDiffService_js_1 = require("./TemplateDiffService.js");
|
|
6
6
|
const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
7
7
|
const WorkspaceSnapshotService_1 = require("./WorkspaceSnapshotService");
|
|
8
|
+
const SelectorSanitizer_1 = require("./SelectorSanitizer");
|
|
8
9
|
class InitializerUpdater {
|
|
9
10
|
/**
|
|
10
11
|
* Mutate a StringLiteral or NoSubstitutionTemplateLiteral node to
|
|
@@ -14,6 +15,15 @@ class InitializerUpdater {
|
|
|
14
15
|
* mutation throws (caller should fall through to existing strategies).
|
|
15
16
|
*/
|
|
16
17
|
static apply(valueNode, sourceFile, newValue) {
|
|
18
|
+
// ABSOLUTE GUARD: never serialise a Playwright internal engine selector
|
|
19
|
+
// into a string-literal definition. `.locator()` strings cannot express
|
|
20
|
+
// a semantic locator (getByRole/…), so writing `internal:role=…` here is
|
|
21
|
+
// the bug that leaks engine internals into user source. Refuse → the
|
|
22
|
+
// caller escalates to a semantic-guard (no regex fallback corruption).
|
|
23
|
+
if ((0, SelectorSanitizer_1.isInternalEngineSelector)(newValue)) {
|
|
24
|
+
console.warn(`[InitializerUpdater] ⛔ refusing to write internal engine selector into source: "${newValue.slice(0, 64)}"`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
17
27
|
try {
|
|
18
28
|
if (ts_morph_1.Node.isStringLiteral(valueNode)) {
|
|
19
29
|
valueNode.setLiteralValue(newValue);
|
|
@@ -24,14 +34,14 @@ class InitializerUpdater {
|
|
|
24
34
|
else {
|
|
25
35
|
return null;
|
|
26
36
|
}
|
|
27
|
-
// Centralised write gate
|
|
37
|
+
// Centralised write gate - mirrors ASTSourceUpdater._persistChanges.
|
|
28
38
|
// InitializerUpdater is the canonical write-head for definition-site
|
|
29
39
|
// mutations (SemanticDispatch → tracer-resolved identifier / property
|
|
30
40
|
// access / cross-file paths all converge here), so the Clean-Room
|
|
31
41
|
// snapshot ledger and DryRunGuard MUST be enforced here too.
|
|
32
42
|
// Without this, an upstream-traced fix (e.g. global StringLiteral at
|
|
33
43
|
// top of file) writes to disk but `WorkspaceSnapshotService` never
|
|
34
|
-
// sees it
|
|
44
|
+
// sees it - `getMutatedSnapshots()` returns empty and PR automation
|
|
35
45
|
// skips the heal entirely.
|
|
36
46
|
const fp = sourceFile.getFilePath();
|
|
37
47
|
if (DryRunGuard_1.DryRunGuard.active()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntentAuditor.d.ts","sourceRoot":"","sources":["../../src/services/IntentAuditor.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAOpD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IAEjC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,qEAAqE;IACrE,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IACzE,yEAAyE;IACzE,cAAc,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E;AAkED;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,GAAE,oBAAyB;IA+BrC,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"IntentAuditor.d.ts","sourceRoot":"","sources":["../../src/services/IntentAuditor.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAOpD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IAEjC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,qEAAqE;IACrE,WAAW,CAAC,EAAE,UAAU,CAAC;CAC1B;AAED,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IACzE,yEAAyE;IACzE,cAAc,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E;AAkED;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,GAAE,oBAAyB;IA+BrC,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAyH3D,sBAAsB,CACpB,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,EAAE,YAAY,EAAE,GACvB,MAAM;IA4CT,kBAAkB,CAChB,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACzC,WAAW,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACzC,MAAM;IAmCT,OAAO,CAAC,YAAY;CA8CrB"}
|
|
@@ -46,15 +46,15 @@ dotenv.config();
|
|
|
46
46
|
const FALLBACK_VERDICT = {
|
|
47
47
|
verdict: "SUSPICIOUS",
|
|
48
48
|
confidence: 0,
|
|
49
|
-
reason: "Auditor unavailable
|
|
49
|
+
reason: "Auditor unavailable - defaulting to cautious SUSPICIOUS verdict",
|
|
50
50
|
inversionType: null,
|
|
51
51
|
};
|
|
52
52
|
// Ancestry drift penalties (spec §2.1).
|
|
53
53
|
// Applied as negative adjustments to the LLM's confidence score.
|
|
54
54
|
// Maximum combined penalty: −30 pts.
|
|
55
55
|
const DRIFT_PENALTIES = [
|
|
56
|
-
{ full: -20, partial: -10 }, // depth 1
|
|
57
|
-
{ full: -10, partial: -5 }, // depth 2
|
|
56
|
+
{ full: -20, partial: -10 }, // depth 1 - direct parent
|
|
57
|
+
{ full: -10, partial: -5 }, // depth 2 - grandparent
|
|
58
58
|
];
|
|
59
59
|
// Anchor bonuses: positive-only (spec §3.3).
|
|
60
60
|
// closestLabel exact match: +10 pts
|
|
@@ -85,8 +85,8 @@ RULES:
|
|
|
85
85
|
3. Minor reformatting is irrelevant. "Save Changes" and "Save" are CONSISTENT.
|
|
86
86
|
4. Role/aria context matters. A button that was "Submit" becoming "Cancel" is INCONSISTENT (identity inversion).
|
|
87
87
|
5. When both texts are very short (3 chars or less) and differ, prefer SUSPICIOUS unless an obvious opposite pair.
|
|
88
|
-
6. In "assertion" mode, be stricter
|
|
89
|
-
7. In "action" mode, be more lenient
|
|
88
|
+
6. In "assertion" mode, be stricter - lean toward SUSPICIOUS or INCONSISTENT when the change is ambiguous.
|
|
89
|
+
7. In "action" mode, be more lenient - lean toward CONSISTENT when the change looks like a UI refactor.
|
|
90
90
|
|
|
91
91
|
RESPONSE FORMAT (strict JSON, no other text):
|
|
92
92
|
{
|
|
@@ -102,7 +102,7 @@ class IntentAuditor {
|
|
|
102
102
|
model;
|
|
103
103
|
constructor(opts = {}) {
|
|
104
104
|
this.model = opts.model ?? DEFAULT_AUDITOR_MODEL;
|
|
105
|
-
// Explicit `null` from the caller forces disabled mode
|
|
105
|
+
// Explicit `null` from the caller forces disabled mode - used by tests.
|
|
106
106
|
if (opts.anthropic === null) {
|
|
107
107
|
this.anthropic = null;
|
|
108
108
|
this.disabled = true;
|
|
@@ -117,7 +117,7 @@ class IntentAuditor {
|
|
|
117
117
|
// Otherwise fall back to env-based construction (legacy default).
|
|
118
118
|
const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
119
119
|
if (!apiKey) {
|
|
120
|
-
console.warn("[IntentAuditor] ANTHROPIC_API_KEY not set
|
|
120
|
+
console.warn("[IntentAuditor] ANTHROPIC_API_KEY not set - auditor disabled, will return SUSPICIOUS for all audited heals");
|
|
121
121
|
this.anthropic = null;
|
|
122
122
|
this.disabled = true;
|
|
123
123
|
}
|
|
@@ -154,7 +154,7 @@ class IntentAuditor {
|
|
|
154
154
|
// ── Structural scoring ────────────────────────────────────────
|
|
155
155
|
// Apply ancestry drift penalty and anchor bonus to the LLM's raw
|
|
156
156
|
// confidence. The semantic verdict category (CONSISTENT / SUSPICIOUS /
|
|
157
|
-
// INCONSISTENT) is never changed by scoring
|
|
157
|
+
// INCONSISTENT) is never changed by scoring - only the confidence value.
|
|
158
158
|
// The low-confidence CONSISTENT→SUSPICIOUS downgrade is re-evaluated
|
|
159
159
|
// after scoring so the adjusted value is used for the threshold.
|
|
160
160
|
const penalty = this.computeAncestryPenalty(req.dnaAncestry ?? [], req.liveAncestry ?? []);
|
|
@@ -172,7 +172,11 @@ class IntentAuditor {
|
|
|
172
172
|
// Re-apply low-confidence CONSISTENT downgrade with the adjusted value.
|
|
173
173
|
// The raw check in parseVerdict used the pre-adjustment confidence; a
|
|
174
174
|
// penalty may push a borderline CONSISTENT below the 50% threshold.
|
|
175
|
-
const scoring = {
|
|
175
|
+
const scoring = {
|
|
176
|
+
rawConfidence: rawVerdict.confidence,
|
|
177
|
+
penalty,
|
|
178
|
+
bonus,
|
|
179
|
+
};
|
|
176
180
|
if (rawVerdict.verdict === "CONSISTENT" && adjConf < 50) {
|
|
177
181
|
finalVerdict = {
|
|
178
182
|
verdict: "SUSPICIOUS",
|
|
@@ -184,22 +188,29 @@ class IntentAuditor {
|
|
|
184
188
|
console.log(`[IntentAuditor] CONSISTENT→SUSPICIOUS after scoring (${rawVerdict.confidence}%→${adjConf}%)`);
|
|
185
189
|
}
|
|
186
190
|
else {
|
|
187
|
-
finalVerdict = {
|
|
191
|
+
finalVerdict = {
|
|
192
|
+
...rawVerdict,
|
|
193
|
+
confidence: adjConf,
|
|
194
|
+
scoreBreakdown: scoring,
|
|
195
|
+
};
|
|
188
196
|
}
|
|
189
197
|
}
|
|
190
198
|
else {
|
|
191
199
|
finalVerdict = rawVerdict;
|
|
192
200
|
}
|
|
193
|
-
console.log(`[IntentAuditor] verdict=${finalVerdict.verdict} (${finalVerdict.confidence}%)
|
|
201
|
+
console.log(`[IntentAuditor] verdict=${finalVerdict.verdict} (${finalVerdict.confidence}%) - ${finalVerdict.reason}`);
|
|
194
202
|
return finalVerdict;
|
|
195
203
|
}
|
|
196
204
|
catch (err) {
|
|
197
|
-
console.warn(`[IntentAuditor] Warning: Auditor call failed: ${err.message}
|
|
198
|
-
return {
|
|
205
|
+
console.warn(`[IntentAuditor] Warning: Auditor call failed: ${err.message} - returning SUSPICIOUS`);
|
|
206
|
+
return {
|
|
207
|
+
...FALLBACK_VERDICT,
|
|
208
|
+
reason: `Auditor call failed: ${err.message}`,
|
|
209
|
+
};
|
|
199
210
|
}
|
|
200
211
|
}
|
|
201
212
|
// ─────────────────────────────────────────────────────────────
|
|
202
|
-
// computeAncestryPenalty
|
|
213
|
+
// computeAncestryPenalty - DNA v2 Ancestry Drift (spec §2)
|
|
203
214
|
//
|
|
204
215
|
// Compares at most 2 ancestor depths between the DNA baseline and
|
|
205
216
|
// the live candidate. Returns a value in [−30, 0]; 0 = no drift.
|
|
@@ -216,7 +227,7 @@ class IntentAuditor {
|
|
|
216
227
|
// ─────────────────────────────────────────────────────────────
|
|
217
228
|
computeAncestryPenalty(dnaPath, livePath) {
|
|
218
229
|
if (dnaPath.length === 0)
|
|
219
|
-
return 0; // v1 migrated snapshot
|
|
230
|
+
return 0; // v1 migrated snapshot - skip
|
|
220
231
|
let totalPenalty = 0;
|
|
221
232
|
const maxDepth = Math.min(dnaPath.length, livePath.length, 2);
|
|
222
233
|
for (let i = 0; i < maxDepth; i++) {
|
|
@@ -229,21 +240,21 @@ class IntentAuditor {
|
|
|
229
240
|
const depthLabel = i === 0 ? "depth-1 (parent)" : "depth-2 (grandparent)";
|
|
230
241
|
if (dna.tag !== live.tag) {
|
|
231
242
|
totalPenalty += full;
|
|
232
|
-
console.log(`[IntentAuditor] 🏗️ Ancestry drift ${full} pts
|
|
243
|
+
console.log(`[IntentAuditor] 🏗️ Ancestry drift ${full} pts - ${depthLabel}: ` +
|
|
233
244
|
`tag mismatch (${dna.fingerprint} → ${live.fingerprint})`);
|
|
234
245
|
}
|
|
235
246
|
else {
|
|
236
247
|
totalPenalty += partial;
|
|
237
|
-
console.log(`[IntentAuditor] 🏗️ Ancestry drift ${partial} pts
|
|
248
|
+
console.log(`[IntentAuditor] 🏗️ Ancestry drift ${partial} pts - ${depthLabel}: ` +
|
|
238
249
|
`same tag, id/class changed (${dna.fingerprint} → ${live.fingerprint})`);
|
|
239
250
|
}
|
|
240
251
|
}
|
|
241
252
|
return totalPenalty;
|
|
242
253
|
}
|
|
243
254
|
// ─────────────────────────────────────────────────────────────
|
|
244
|
-
// computeAnchorBonus
|
|
255
|
+
// computeAnchorBonus - DNA v2 Anchor Confirmation (spec §3.3)
|
|
245
256
|
//
|
|
246
|
-
// Anchors only provide positive confirmation
|
|
257
|
+
// Anchors only provide positive confirmation - no penalty for
|
|
247
258
|
// mismatch. Returns a value in [0, +15].
|
|
248
259
|
//
|
|
249
260
|
// closestLabel exact match (case-insensitive): +10 pts
|
|
@@ -257,13 +268,13 @@ class IntentAuditor {
|
|
|
257
268
|
const liveLabel = liveAnchors.closestLabel?.toLowerCase().trim();
|
|
258
269
|
if (dnaLabel && liveLabel && dnaLabel === liveLabel) {
|
|
259
270
|
bonus += ANCHOR_BONUS_LABEL;
|
|
260
|
-
console.log(`[IntentAuditor] 🎯 Anchor +${ANCHOR_BONUS_LABEL} pts
|
|
271
|
+
console.log(`[IntentAuditor] 🎯 Anchor +${ANCHOR_BONUS_LABEL} pts - closestLabel match: "${dnaAnchors.closestLabel}"`);
|
|
261
272
|
}
|
|
262
273
|
const dnaRow = dnaAnchors.rowAnchor?.toLowerCase().trim();
|
|
263
274
|
const liveRow = liveAnchors.rowAnchor?.toLowerCase().trim();
|
|
264
275
|
if (dnaRow && liveRow && dnaRow === liveRow) {
|
|
265
276
|
bonus += ANCHOR_BONUS_ROW;
|
|
266
|
-
console.log(`[IntentAuditor] 🎯 Anchor +${ANCHOR_BONUS_ROW} pts
|
|
277
|
+
console.log(`[IntentAuditor] 🎯 Anchor +${ANCHOR_BONUS_ROW} pts - rowAnchor match: "${dnaAnchors.rowAnchor}"`);
|
|
267
278
|
}
|
|
268
279
|
return bonus;
|
|
269
280
|
}
|