sela-core 0.1.0-alpha.2 → 0.1.0-alpha.31

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.
@@ -5,6 +5,11 @@ export declare class ConfigLoader {
5
5
  /**
6
6
  * Returns the fully resolved config. First call does all I/O; subsequent
7
7
  * calls return the cached result at zero cost.
8
+ *
9
+ * Throws when:
10
+ * - `SELA_CONFIG_PATH` is set but points to a non-existent file.
11
+ * - A config file is found but cannot be loaded or has invalid keys.
12
+ * - `SELA_DRY_RUN` / `SELA_NO_CACHE` contain an unrecognised value.
8
13
  */
9
14
  static getInstance(): ResolvedConfig;
10
15
  private static logEnvBanners;
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,cAAc,EAAiB,MAAM,cAAc,CAAC;AA6GzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAE5C;;;OAGG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAuCpC,OAAO,CAAC,MAAM,CAAC,aAAa;IAW5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAMjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAIrB"}
1
+ {"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,cAAc,EAAiB,MAAM,cAAc,CAAC;AAwLzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAE5C;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAmCpC,OAAO,CAAC,MAAM,CAAC,aAAa;IAW5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAIrB"}
@@ -43,10 +43,41 @@ const path = __importStar(require("path"));
43
43
  const child_process_1 = require("child_process");
44
44
  const SelaConfig_1 = require("./SelaConfig");
45
45
  const logger_1 = require("../utils/logger");
46
+ // All public top-level keys of SelaConfig — used to catch typos at load time.
47
+ const KNOWN_CONFIG_KEYS = new Set([
48
+ "healingPolicy",
49
+ "thresholds",
50
+ "agenticDiscovery",
51
+ "updateStrategy",
52
+ "autoCommit",
53
+ "prAutomation",
54
+ "dnaStoragePath",
55
+ "allowedPaths",
56
+ "maxHealsPerRun",
57
+ "sanitization",
58
+ "branchOverrides",
59
+ ]);
46
60
  // ─────────────────────────────────────────────────────────────────
47
61
  // FILE DISCOVERY
48
62
  // ─────────────────────────────────────────────────────────────────
63
+ /**
64
+ * Resolves the config file path.
65
+ *
66
+ * Resolution order:
67
+ * 1. `SELA_CONFIG_PATH` env var — absolute or cwd-relative; throws immediately
68
+ * if set but the file does not exist (typo in CI config is a hard error).
69
+ * 2. Upward traversal from `startDir`, checking both .ts and .js extensions,
70
+ * stopping at the filesystem root.
71
+ */
49
72
  function findConfigFile(startDir) {
73
+ const envPath = process.env.SELA_CONFIG_PATH;
74
+ if (envPath) {
75
+ const resolved = path.resolve(envPath);
76
+ if (fs.existsSync(resolved))
77
+ return resolved;
78
+ throw new Error(`[Sela] SELA_CONFIG_PATH points to a non-existent file: "${resolved}". ` +
79
+ `Check the env var in your CI configuration.`);
80
+ }
50
81
  let dir = startDir;
51
82
  while (true) {
52
83
  for (const name of ["sela.config.ts", "sela.config.js"]) {
@@ -65,13 +96,11 @@ function findConfigFile(startDir) {
65
96
  // ─────────────────────────────────────────────────────────────────
66
97
  function getCurrentBranch() {
67
98
  try {
68
- // Fail fast if not a git repo or git is unavailable
69
99
  const branch = (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD", {
70
100
  encoding: "utf-8",
71
101
  stdio: ["pipe", "pipe", "pipe"],
72
102
  }).trim();
73
103
  if (branch === "HEAD") {
74
- // Detached HEAD (common in CI with explicit SHA checkout)
75
104
  logger_1.logger.warn("Detached HEAD detected - branch overrides skipped");
76
105
  return null;
77
106
  }
@@ -83,25 +112,64 @@ function getCurrentBranch() {
83
112
  }
84
113
  }
85
114
  // ─────────────────────────────────────────────────────────────────
115
+ // STRUCTURAL VALIDATION
116
+ // ─────────────────────────────────────────────────────────────────
117
+ function validateConfigKeys(config, filePath) {
118
+ const unknown = Object.keys(config).filter((k) => !KNOWN_CONFIG_KEYS.has(k));
119
+ if (unknown.length > 0) {
120
+ throw new Error(`[Sela] Unknown key(s) in ${path.basename(filePath)}: ` +
121
+ `${unknown.map((k) => `"${k}"`).join(", ")}. ` +
122
+ `Valid top-level keys: ${[...KNOWN_CONFIG_KEYS].join(", ")}.`);
123
+ }
124
+ }
125
+ // ─────────────────────────────────────────────────────────────────
86
126
  // CONFIG FILE LOADING
87
127
  // ─────────────────────────────────────────────────────────────────
128
+ /**
129
+ * Loads and validates the raw config from `configPath`.
130
+ *
131
+ * Load strategy (in order):
132
+ * 1. `configPath` as-is — works when a TS loader is registered
133
+ * (Playwright, ts-node, tsx).
134
+ * 2. `.js` sibling — for compiled CI builds where only the transpiled file
135
+ * is present alongside the source `.ts`.
136
+ *
137
+ * Throws (never silently returns {}) when the file is found but every load
138
+ * attempt fails, so CI surfaces the root cause instead of running on defaults.
139
+ */
88
140
  function loadRawConfig(configPath) {
89
- try {
90
- // Works in Playwright test context (TypeScript loader registered by @playwright/test)
91
- // and in Jest context (ts-jest). Falls back gracefully if neither is active.
92
- // eslint-disable-next-line @typescript-eslint/no-var-requires
93
- const mod = require(configPath);
94
- const config = mod?.default ?? mod ?? {};
95
- if (typeof config !== "object" || config === null) {
96
- logger_1.logger.warn(`sela.config did not export an object - using defaults`);
97
- return {};
98
- }
99
- return config;
141
+ const candidates = [configPath];
142
+ if (configPath.endsWith(".ts")) {
143
+ candidates.push(configPath.replace(/\.ts$/, ".js"));
100
144
  }
101
- catch (err) {
102
- logger_1.logger.warn(`Failed to load ${path.basename(configPath)}: ${err.message}`);
103
- return {};
145
+ let lastError = null;
146
+ for (const candidate of candidates) {
147
+ if (!fs.existsSync(candidate))
148
+ continue;
149
+ try {
150
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
151
+ const mod = require(candidate);
152
+ const config = mod?.default ?? mod ?? {};
153
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
154
+ throw new Error(`${path.basename(candidate)} must export a plain object — ` +
155
+ `got ${Array.isArray(config) ? "array" : typeof config}`);
156
+ }
157
+ validateConfigKeys(config, candidate);
158
+ return config;
159
+ }
160
+ catch (err) {
161
+ // Key-validation failures are definitive — skip fallback candidates.
162
+ if (err.message?.startsWith("[Sela]"))
163
+ throw err;
164
+ lastError = err;
165
+ }
104
166
  }
167
+ // Every attempt failed — throw with a diagnostic instead of silently
168
+ // falling back to defaults. Missing sela.config → defaults is intentional
169
+ // (handled at the call site); found-but-broken is always a hard error.
170
+ throw new Error(`[Sela] Failed to load ${path.basename(configPath)}: ${lastError?.message ?? "unknown error"}. ` +
171
+ `Ensure a TypeScript loader (Playwright, ts-node, tsx) is active, ` +
172
+ `or provide a compiled sela.config.js alongside sela.config.ts.`);
105
173
  }
106
174
  // ─────────────────────────────────────────────────────────────────
107
175
  // DEEP MERGE (plain objects only - arrays are replaced, not merged)
@@ -134,20 +202,23 @@ class ConfigLoader {
134
202
  /**
135
203
  * Returns the fully resolved config. First call does all I/O; subsequent
136
204
  * calls return the cached result at zero cost.
205
+ *
206
+ * Throws when:
207
+ * - `SELA_CONFIG_PATH` is set but points to a non-existent file.
208
+ * - A config file is found but cannot be loaded or has invalid keys.
209
+ * - `SELA_DRY_RUN` / `SELA_NO_CACHE` contain an unrecognised value.
137
210
  */
138
211
  static getInstance() {
139
212
  if (ConfigLoader.resolved)
140
213
  return ConfigLoader.resolved;
141
214
  const configPath = findConfigFile(process.cwd());
142
215
  if (!configPath) {
143
- // Zero-breaking-change guarantee: no config → pure defaults
144
216
  ConfigLoader.resolved = (0, SelaConfig_1.resolveConfig)({});
145
217
  ConfigLoader.logEnvBanners(ConfigLoader.resolved);
146
218
  return ConfigLoader.resolved;
147
219
  }
148
220
  logger_1.logger.debug(`Loaded config from: ${path.relative(process.cwd(), configPath)}`);
149
221
  let rawConfig = loadRawConfig(configPath);
150
- // Apply branch overrides using exact-string matching
151
222
  const detectedBranch = getCurrentBranch();
152
223
  ConfigLoader.branch = detectedBranch;
153
224
  if (rawConfig.branchOverrides && detectedBranch) {
@@ -171,7 +242,6 @@ class ConfigLoader {
171
242
  }
172
243
  /** Returns the detected Git branch name, or null if detection failed. */
173
244
  static getBranch() {
174
- // Ensure getInstance() has been called at least once
175
245
  if (!ConfigLoader.resolved)
176
246
  ConfigLoader.getInstance();
177
247
  return ConfigLoader.branch;
@@ -1 +1 @@
1
- {"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAexC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AA0FhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;;IAuBnD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA0DnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IA8qBlB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAgDjC,OAAO,CAAC,kBAAkB;IA8D1B,OAAO,CAAC,oBAAoB;IAyD5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAgLtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAqBnB;AAED,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAexC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AA0FhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;;IAuBnD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA0DnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAirBlB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAgDjC,OAAO,CAAC,kBAAkB;IA8D1B,OAAO,CAAC,oBAAoB;IAyD5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAgLtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAqBnB;AAED,OAAO,EAAE,CAAC"}
@@ -481,6 +481,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
481
481
  // branch / autoCommit / dry-run keep the legacy git-aware path).
482
482
  // Falls back to the legacy regex/semantic engine on ABORT / PROMPT.
483
483
  let updateResult;
484
+ let composedMicroDiff;
484
485
  const plainInPlace = (!this.config.updateStrategy ||
485
486
  this.config.updateStrategy === "inPlace") &&
486
487
  !this.config.autoCommit;
@@ -500,6 +501,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
500
501
  composed.outcome?.status === "applied" &&
501
502
  (strat === "MUTATE_IN_PLACE" || strat === "FORK_AT_TEST")) {
502
503
  WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.postWrite(resolvedForCompose);
504
+ composedMicroDiff = composed.outcome?.microDiff;
503
505
  const wl = composed.writtenLine ?? line;
504
506
  logger_1.logger.stage("ANALYZING AST", `composeHeal primary path (${strat})`);
505
507
  updateResult = {
@@ -550,7 +552,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
550
552
  // any developer dirty edits sit identically in both buffers and
551
553
  // therefore cancel out. Falls back to "" when the snapshot is
552
554
  // missing (cross-file healing into an untracked path, etc.).
553
- const gitUnifiedDiff = this._captureIsolatedDiff(mutatedFilePath);
555
+ const gitUnifiedDiff = composedMicroDiff || this._captureIsolatedDiff(mutatedFilePath);
554
556
  const firstChange = this._extractFirstChangeFromDiff(gitUnifiedDiff);
555
557
  // Backward-compat: keep oldCodeLine/newCodeLine populated from the
556
558
  // first -/+ pair inside the diff. When git produced no diff (file
@@ -63,6 +63,10 @@ export declare const SAFETY_GUARD_CODES: {
63
63
  readonly BLOCKED_ROLE_SWAP_ASSERTION: "SG_BLOCKED_ROLE_SWAP_ASSERTION";
64
64
  readonly FAIL_HARD_AUDITOR_INCONSISTENT: "SG_FAIL_HARD_AUDITOR_INCONSISTENT";
65
65
  readonly BLOCKED_AUDITOR_SUSPICIOUS: "SG_BLOCKED_AUDITOR_SUSPICIOUS";
66
+ /** Structural pre-LLM check: old element had a destructive/auth role and
67
+ * the candidate does not — replacing a DELETE/AUTH action is always
68
+ * semantically inconsistent regardless of AI confidence. */
69
+ readonly FAIL_HARD_DESTRUCTIVE_ROLE_INVERSION: "SG_FAIL_HARD_DESTRUCTIVE_ROLE_INVERSION";
66
70
  /**
67
71
  * Heal was REQUIRES_REVIEW → quarantined (decision #1): proposed but not
68
72
  * written to source and not executed this run, pending explicit approval.
@@ -1 +1 @@
1
- {"version":3,"file":"SelaError.d.ts","sourceRoot":"","sources":["../../src/errors/SelaError.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SelaError.d.ts","sourceRoot":"","sources":["../../src/errors/SelaError.ts"],"names":[],"mappings":"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"}
@@ -94,6 +94,10 @@ exports.SAFETY_GUARD_CODES = {
94
94
  BLOCKED_ROLE_SWAP_ASSERTION: "SG_BLOCKED_ROLE_SWAP_ASSERTION",
95
95
  FAIL_HARD_AUDITOR_INCONSISTENT: "SG_FAIL_HARD_AUDITOR_INCONSISTENT",
96
96
  BLOCKED_AUDITOR_SUSPICIOUS: "SG_BLOCKED_AUDITOR_SUSPICIOUS",
97
+ /** Structural pre-LLM check: old element had a destructive/auth role and
98
+ * the candidate does not — replacing a DELETE/AUTH action is always
99
+ * semantically inconsistent regardless of AI confidence. */
100
+ FAIL_HARD_DESTRUCTIVE_ROLE_INVERSION: "SG_FAIL_HARD_DESTRUCTIVE_ROLE_INVERSION",
97
101
  /**
98
102
  * Heal was REQUIRES_REVIEW → quarantined (decision #1): proposed but not
99
103
  * written to source and not executed this run, pending explicit approval.
@@ -1 +1 @@
1
- {"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"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"}
@@ -109,7 +109,17 @@ intended element on the next test run. Use this rubric:
109
109
  - 90-100: stable id, data-testid, or unique role+name - zero ambiguity in the DOM.
110
110
  - 70-89: strong semantic match, but some structural drift or competing candidates.
111
111
  - 50-69: best-guess match - multiple plausible candidates required disambiguation.
112
- - 1-49: speculative match - return this only when no better candidate exists.
112
+ - 20-49: speculative match - return this only when no better candidate exists.
113
+ - 1-19: SEMANTIC MISMATCH - the best candidate in the DOM has a different action
114
+ type or intent than the original (e.g. original was "Delete" but you can
115
+ only find "Update", or the element is completely absent from the DOM).
116
+ Use this range whenever the semantic role/purpose of the match differs
117
+ from what the DNA describes.
118
+ CRITICAL: The 90-100 range is ONLY valid when the match is unambiguous AND semantically
119
+ identical to the original element. If there is ANY doubt about semantic equivalence
120
+ (different button text implying a different action, different role, different purpose),
121
+ you MUST score below 70. A high confidence on a semantically wrong match is a
122
+ critical failure - it will silently corrupt a test suite.
113
123
  NEVER omit this field. NEVER return it as a string, boolean, or null.
114
124
  Reports rendered downstream will show "n/a" when this field is missing - which
115
125
  is a clear failure signal to the developer reviewing the heal.
@@ -117,10 +127,23 @@ is a clear failure signal to the developer reviewing the heal.
117
127
  ### MANDATORY OUTPUT FORMAT
118
128
  Return ONLY valid JSON.
119
129
 
120
- Example output - Identifying a specific button in a list row:
130
+ Example output A - Unambiguous match via stable testid (confidence 90-100):
121
131
  {
122
132
  "status": "FIXED",
123
- "confidence": 92,
133
+ "confidence": 95,
134
+ "chainSegments": [
135
+ { "type": "getByTestId", "testId": "submit-order-btn" }
136
+ ],
137
+ "segments": [
138
+ { "type": "element", "selector": "[data-testid=\"submit-order-btn\"]" }
139
+ ],
140
+ "explanation": "Element found by stable data-testid - zero ambiguity."
141
+ }
142
+
143
+ Example output B - Semantic drift with structural change (confidence 70-89):
144
+ {
145
+ "status": "FIXED",
146
+ "confidence": 74,
124
147
  "chainSegments": [
125
148
  { "type": "locator", "selector": ".user-item" },
126
149
  { "type": "filter", "hasText": "Bob Smith" },
@@ -132,7 +155,20 @@ Example output - Identifying a specific button in a list row:
132
155
  "selector": ".user-item >> internal:has-text=\"Bob Smith\"i >> internal:role=button[name=\"Modify Bob\"i]"
133
156
  }
134
157
  ],
135
- "explanation": "Updated container to .user-item and targeted the button by its new semantic name 'Modify Bob' to ensure a unique match."
158
+ "explanation": "Button name changed from 'Edit Bob' to 'Modify Bob' - semantically equivalent action, structural drift in container class."
159
+ }
160
+
161
+ Example output C - Semantic mismatch, original element absent (confidence 1-19):
162
+ {
163
+ "status": "FIXED",
164
+ "confidence": 12,
165
+ "chainSegments": [
166
+ { "type": "getByRole", "role": "button", "name": "Update System" }
167
+ ],
168
+ "segments": [
169
+ { "type": "element", "selector": "internal:role=button[name=\"Update System\"i]" }
170
+ ],
171
+ "explanation": "Original 'Delete System' button is absent from the DOM. Nearest candidate is 'Update System' which performs a different action - semantic mismatch, very low confidence."
136
172
  }`;
137
173
  // ─────────────────────────────────────────────────────────────────
138
174
  // HELPERS
@@ -33,6 +33,8 @@ export interface ApplyOutcome {
33
33
  skipped: Array<{
34
34
  reason: string;
35
35
  }>;
36
+ /** Isolated per-batch unified diff — only set when status === "applied". */
37
+ microDiff?: string;
36
38
  }
37
39
  export interface ApplyOptions {
38
40
  /** When false, compute + gate but never touch disk. Default true. */
@@ -1 +1 @@
1
- {"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"AA2BA,OAAO,EACL,SAAS,EAET,UAAU,EACX,MAAM,kBAAkB,CAAC;AAY1B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAeD,qBAAa,eAAe;IAC1B,gEAAgE;IAChE,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,cAAc,CACnB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,YAAiB,GACtB,YAAY;IAqCf,OAAO,CAAC,MAAM,CAAC,sBAAsB;CA2KtC;AA2CD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB5D"}
1
+ {"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,SAAS,EAET,UAAU,EACX,MAAM,kBAAkB,CAAC;AAY1B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAeD,qBAAa,eAAe;IAC1B,gEAAgE;IAChE,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,cAAc,CACnB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,YAAiB,GACtB,YAAY;IAqCf,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAiLtC;AA2CD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB5D"}
@@ -60,7 +60,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
60
60
  exports.MutationApplier = void 0;
61
61
  exports.isLocatorReturningExpr = isLocatorReturningExpr;
62
62
  const fs = __importStar(require("fs"));
63
+ const path = __importStar(require("path"));
63
64
  const ts_morph_1 = require("ts-morph");
65
+ const IsolatedDiff_1 = require("../utils/IsolatedDiff");
64
66
  const AnchorResolver_1 = require("./AnchorResolver");
65
67
  const DecisionEngine_1 = require("./DecisionEngine");
66
68
  const TraceBackEngine_1 = require("./TraceBackEngine");
@@ -236,6 +238,10 @@ class MutationApplier {
236
238
  skipped,
237
239
  };
238
240
  }
241
+ const microDiff = (0, IsolatedDiff_1.generateUnifiedDiff)(original, newContent, {
242
+ filePath: path.relative(process.cwd(), filePath),
243
+ context: 3,
244
+ }) || undefined;
239
245
  // ── Type-safety gate: baseline-diff diagnostics ──────────────────
240
246
  const afterSf = project.createSourceFile(filePath, newContent, {
241
247
  overwrite: true,
@@ -286,6 +292,7 @@ class MutationApplier {
286
292
  written: write && !dryRun,
287
293
  finalContent: newContent,
288
294
  skipped,
295
+ microDiff,
289
296
  };
290
297
  }
291
298
  }
@@ -115,6 +115,7 @@ export declare class SafetyGuard {
115
115
  constructor(arg?: ResolvedThresholds | SafetyGuardOptions);
116
116
  private static normalizeArgs;
117
117
  evaluate(context: HealContext, aiFix: AIFixSummary): Promise<SafetyDecision>;
118
+ private applyTextDriftGuard;
118
119
  classifyFunctionalRole(text: string): FunctionalRole;
119
120
  }
120
121
  //# sourceMappingURL=SafetyGuard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SafetyGuard.d.ts","sourceRoot":"","sources":["../../src/services/SafetyGuard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SafetyGuard.d.ts","sourceRoot":"","sources":["../../src/services/SafetyGuard.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ const IntentAuditor_1 = require("./IntentAuditor");
7
7
  const PipelineContext_1 = require("./safety/PipelineContext");
8
8
  const rules_1 = require("./safety/rules");
9
9
  const roleClassifier_1 = require("./safety/roleClassifier");
10
+ const logger_1 = require("../utils/logger");
10
11
  function dispositionFor(level) {
11
12
  switch (level) {
12
13
  case "SAFE":
@@ -61,6 +62,24 @@ class SafetyGuard {
61
62
  // ─────────────────────────────────────────────────────────────
62
63
  async evaluate(context, aiFix) {
63
64
  const ctx = (0, PipelineContext_1.buildContext)(context, aiFix, this.thresholds, this.intentAuditor);
65
+ // ── Text-Drift Pre-Flight ─────────────────────────────────────
66
+ // Compare DNA baseline text vs live candidate text BEFORE any rule
67
+ // runs. This is the primary defence against the "Delete System →
68
+ // Update System same-container mis-identification" class of bug:
69
+ //
70
+ // 1. Drift confirmed (both texts present, differ):
71
+ // synthesise effectiveContentChange so StructuralOnlyRule
72
+ // cannot short-circuit to SAFE, and set forceAudit so the
73
+ // Intent Auditor is mandatory regardless of confidence.
74
+ //
75
+ // 2. Drift unverifiable (DNA text present but liveText absent
76
+ // AND AI did not self-report a contentChange):
77
+ // set forceAudit so StructuralOnlyRule downgrades SAFE → HOLD.
78
+ //
79
+ // This guard runs once, synchronously, before the rule loop.
80
+ // ZeroTrustRule (step 2) may re-derive effectiveContentChange from
81
+ // the same inputs; that is harmless — it will set the same value.
82
+ this.applyTextDriftGuard(ctx, aiFix);
64
83
  for (const rule of rules_1.SAFETY_RULES) {
65
84
  const decision = await rule.run(ctx);
66
85
  if (decision) {
@@ -73,6 +92,28 @@ class SafetyGuard {
73
92
  // Unreachable: ConfidenceTierRule is terminal and always decides.
74
93
  throw new Error("[SafetyGuard] rule pipeline exhausted without a decision - this is a bug");
75
94
  }
95
+ applyTextDriftGuard(ctx, aiFix) {
96
+ const dnaText = aiFix.dnaBaselineText?.trim();
97
+ const liveText = aiFix.liveText?.trim();
98
+ if (dnaText && liveText && dnaText !== liveText) {
99
+ // Drift confirmed — synthesise contentChange if ZeroTrustRule has not
100
+ // yet run (it will run next and will produce the same pair; no conflict).
101
+ if (!ctx.effectiveContentChange) {
102
+ ctx.effectiveContentChange = { oldText: dnaText, newText: liveText };
103
+ }
104
+ ctx.forceAudit = true;
105
+ logger_1.logger.warn(`SafetyGuard text-drift pre-flight: DNA="${dnaText}" ≠ live="${liveText}" — ` +
106
+ `forceAudit=true, Intent Auditor is mandatory`);
107
+ }
108
+ else if (dnaText && !liveText && !aiFix.contentChange) {
109
+ // Cannot verify: DNA baseline exists but live text could not be fetched
110
+ // and the AI did not self-report a contentChange. Conservative stance:
111
+ // force a hold so StructuralOnlyRule cannot silently approve as SAFE.
112
+ ctx.forceAudit = true;
113
+ logger_1.logger.warn(`SafetyGuard text-drift pre-flight: liveText unavailable, ` +
114
+ `DNA="${dnaText}" unverified — forceAudit=true`);
115
+ }
116
+ }
76
117
  // ─────────────────────────────────────────────────────────────
77
118
  // FUNCTIONAL ROLE CLASSIFICATION (public for tests / external use)
78
119
  //
@@ -1,10 +1,18 @@
1
1
  import type { FunctionalRole } from "../roleClassifier";
2
2
  import type { SafetyRule } from "../PipelineContext";
3
3
  /**
4
- * Roles whose ARRIVAL is treated as high-risk: a control that did not
5
- * obviously mean "delete" or "authenticate" before, but now does.
4
+ * Roles whose arrival OR departure is treated as high-risk.
6
5
  */
7
6
  export declare const DANGEROUS_SINK_ROLES: ReadonlySet<FunctionalRole>;
7
+ /** Transition INTO a dangerous role (e.g. UNKNOWN → DELETE). */
8
8
  export declare function isDangerousSinkTransition(oldRole: FunctionalRole, newRole: FunctionalRole): boolean;
9
+ /**
10
+ * Transition OUT OF a dangerous role (e.g. DELETE → UNKNOWN / SUBMIT).
11
+ *
12
+ * Replacing a destructive/auth control with any other role is semantically
13
+ * inconsistent: the test was authored against a destructive action and the
14
+ * proposed candidate performs a different operation.
15
+ */
16
+ export declare function isDangerousSourceTransition(oldRole: FunctionalRole, newRole: FunctionalRole): boolean;
9
17
  export declare const DangerousSinkRule: SafetyRule;
10
18
  //# sourceMappingURL=DangerousSinkRule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DangerousSinkRule.d.ts","sourceRoot":"","sources":["../../../../src/services/safety/rules/DangerousSinkRule.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAqC,MAAM,oBAAoB,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,cAAc,CAG3D,CAAC;AAEH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,cAAc,GACtB,OAAO,CAET;AAED,eAAO,MAAM,iBAAiB,EAAE,UAmB/B,CAAC"}
1
+ {"version":3,"file":"DangerousSinkRule.d.ts","sourceRoot":"","sources":["../../../../src/services/safety/rules/DangerousSinkRule.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAqC,MAAM,oBAAoB,CAAC;AAExF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,cAAc,CAG3D,CAAC;AAEH,gEAAgE;AAChE,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,cAAc,GACtB,OAAO,CAET;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,cAAc,GACtB,OAAO,CAET;AAED,eAAO,MAAM,iBAAiB,EAAE,UA4B/B,CAAC"}
@@ -1,40 +1,55 @@
1
1
  "use strict";
2
2
  // src/services/safety/rules/DangerousSinkRule.ts
3
3
  //
4
- // Step 5.5 - Dangerous-Sink escalation (context-mutator, returns null).
4
+ // Step 5.5 - Dangerous-Sink / Dangerous-Source escalation (context-mutator,
5
+ // returns null).
5
6
  //
6
- // Closes the headline blind spot from the incident: a heal that transitions
7
- // INTO a destructive or auth role (e.g. "Update System"(UNKNOWN) →
8
- // "delete System"(DELETE)) sailed through as SAFE because:
9
- // - the cross-role swap rule (step 5) only fires when BOTH roles are known,
10
- // - the auditor gate (step 6) only fired for zero-trust / assertion /
11
- // both-UNKNOWN, none of which matched.
7
+ // Closes two related blind spots:
8
+ //
9
+ // SINK (X DELETE/AUTH): a control that did not obviously mean "delete"
10
+ // or "authenticate" before, but now does. The cross-role swap rule
11
+ // (step 5) only fires when BOTH roles are known; this catches
12
+ // UNKNOWN DELETE.
13
+ //
14
+ // SOURCE (DELETE/AUTH → X): replacing a destructive/auth control with a
15
+ // non-destructive one (e.g. "Delete System" → "Update System")
16
+ // is equally dangerous — it masks the test's original destructive
17
+ // intent. FunctionalRoleRule misses this because newRole=UNKNOWN
18
+ // fails its "both roles known" guard.
12
19
  //
13
20
  // Per design decision #2, this rule does NOT decide on its own; it FORCES
14
- // the Intent Auditor to run (ctx.forceAudit = true) whenever the new role is
15
- // a dangerous sink and differs from the old role. The auditor's existing
21
+ // the Intent Auditor to run (ctx.forceAudit = true). The auditor's existing
16
22
  // verdict mapping then applies: INCONSISTENT → FAIL_HARD, SUSPICIOUS →
17
- // BLOCKED/REQUIRES_REVIEW, CONSISTENT → continue.
18
- //
19
- // Note on coverage: a swap between two KNOWN roles (e.g. SUBMIT → DELETE)
20
- // is already short-circuited by FunctionalRoleRule before reaching here, so
21
- // in practice this rule fires for the UNKNOWN→sink case it was built for.
23
+ // BLOCKED/REQUIRES_REVIEW, CONSISTENT → continue. In practice the
24
+ // pre-LLM structural check in IntentAuditorRule will FAIL_HARD immediately
25
+ // for SOURCE transitions before the LLM is even consulted.
22
26
  Object.defineProperty(exports, "__esModule", { value: true });
23
27
  exports.DangerousSinkRule = exports.DANGEROUS_SINK_ROLES = void 0;
24
28
  exports.isDangerousSinkTransition = isDangerousSinkTransition;
29
+ exports.isDangerousSourceTransition = isDangerousSourceTransition;
25
30
  const roleClassifier_1 = require("../roleClassifier");
26
31
  const logger_1 = require("../../../utils/logger");
27
32
  /**
28
- * Roles whose ARRIVAL is treated as high-risk: a control that did not
29
- * obviously mean "delete" or "authenticate" before, but now does.
33
+ * Roles whose arrival OR departure is treated as high-risk.
30
34
  */
31
35
  exports.DANGEROUS_SINK_ROLES = new Set([
32
36
  "DELETE",
33
37
  "AUTH",
34
38
  ]);
39
+ /** Transition INTO a dangerous role (e.g. UNKNOWN → DELETE). */
35
40
  function isDangerousSinkTransition(oldRole, newRole) {
36
41
  return exports.DANGEROUS_SINK_ROLES.has(newRole) && newRole !== oldRole;
37
42
  }
43
+ /**
44
+ * Transition OUT OF a dangerous role (e.g. DELETE → UNKNOWN / SUBMIT).
45
+ *
46
+ * Replacing a destructive/auth control with any other role is semantically
47
+ * inconsistent: the test was authored against a destructive action and the
48
+ * proposed candidate performs a different operation.
49
+ */
50
+ function isDangerousSourceTransition(oldRole, newRole) {
51
+ return exports.DANGEROUS_SINK_ROLES.has(oldRole) && newRole !== oldRole;
52
+ }
38
53
  exports.DangerousSinkRule = {
39
54
  id: "dangerous-sink-escalation",
40
55
  run(ctx) {
@@ -48,6 +63,12 @@ exports.DangerousSinkRule = {
48
63
  `forcing Intent Auditor`);
49
64
  ctx.forceAudit = true;
50
65
  }
66
+ if (isDangerousSourceTransition(oldRole, newRole)) {
67
+ logger_1.logger.warn(`SafetyGuard dangerous-source transition: ` +
68
+ `"${ctx.oldText}" (${oldRole}) → "${ctx.newText}" (${newRole}) - ` +
69
+ `outbound from destructive role, forcing Intent Auditor`);
70
+ ctx.forceAudit = true;
71
+ }
51
72
  return null;
52
73
  },
53
74
  };
@@ -1 +1 @@
1
- {"version":3,"file":"IntentAuditorRule.d.ts","sourceRoot":"","sources":["../../../../src/services/safety/rules/IntentAuditorRule.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAqC,MAAM,oBAAoB,CAAC;AAExF,eAAO,MAAM,iBAAiB,EAAE,UAoG/B,CAAC"}
1
+ {"version":3,"file":"IntentAuditorRule.d.ts","sourceRoot":"","sources":["../../../../src/services/safety/rules/IntentAuditorRule.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,UAAU,EAAqC,MAAM,oBAAoB,CAAC;AAExF,eAAO,MAAM,iBAAiB,EAAE,UAoI/B,CAAC"}