@selfcure/selfcure 0.1.0
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 +17 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +71 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @selfcure/selfcure
|
|
2
|
+
|
|
3
|
+
> Self-healing loop for **selfcure** — feeds trace + error to the configured LLM, applies a diff, and re-validates.
|
|
4
|
+
|
|
5
|
+
Part of selfcure's **legacy BYOK pipeline** (fallback for teams not using the Playwright Test Agents' Healer). On a failing test, it sends the trace and error to the configured provider, applies the proposed diff, and re-runs to confirm the fix. **BYOK** — bring your own key.
|
|
6
|
+
|
|
7
|
+
Internal library powering `selfcure heal`. The headline, preventive flow is `selfcure lint` — see [`@selfcure/cli`](https://www.npmjs.com/package/@selfcure/cli).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @selfcure/selfcure
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Docs
|
|
16
|
+
|
|
17
|
+
Full documentation: https://github.com/ricardofrancocustodio/selfcure#readme
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AIConfig } from '@selfcure/generator';
|
|
2
|
+
import { TestResult } from '@selfcure/runner';
|
|
3
|
+
|
|
4
|
+
interface HealOptions {
|
|
5
|
+
/** Resolved `ai` block from selfcure.config.mjs — provider + model overrides */
|
|
6
|
+
ai: AIConfig;
|
|
7
|
+
/** How many times to attempt patching before giving up */
|
|
8
|
+
maxAttempts?: number;
|
|
9
|
+
/** Playwright config needed to re-run after patching */
|
|
10
|
+
playwrightConfig: string;
|
|
11
|
+
}
|
|
12
|
+
interface HealResult {
|
|
13
|
+
filePath: string;
|
|
14
|
+
healed: boolean;
|
|
15
|
+
attempts: number;
|
|
16
|
+
/** Unified diff that was ultimately applied, if any */
|
|
17
|
+
patchApplied?: string;
|
|
18
|
+
finalError?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* For each failing test result, ask the configured LLM for a diff, apply it,
|
|
22
|
+
* and re-validate by reading the patched file. Rejects the patch and rolls
|
|
23
|
+
* back if the file cannot be parsed after patching.
|
|
24
|
+
*/
|
|
25
|
+
declare function heal(failedTests: TestResult[], options: HealOptions): Promise<HealResult[]>;
|
|
26
|
+
|
|
27
|
+
export { type HealOptions, type HealResult, heal as default, heal };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { generateText } from "ai";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import { getModel } from "@selfcure/generator";
|
|
5
|
+
function buildHealPrompt(result, source) {
|
|
6
|
+
return `You are an expert Playwright test engineer fixing a failing test.
|
|
7
|
+
|
|
8
|
+
## Failing test file
|
|
9
|
+
\`\`\`typescript
|
|
10
|
+
${source}
|
|
11
|
+
\`\`\`
|
|
12
|
+
|
|
13
|
+
## Error
|
|
14
|
+
\`\`\`
|
|
15
|
+
${result.error ?? "Unknown error"}
|
|
16
|
+
\`\`\`
|
|
17
|
+
|
|
18
|
+
Produce a unified diff (--- a/test +++ b/test) that fixes the failure.
|
|
19
|
+
Output ONLY the diff, no explanation.`;
|
|
20
|
+
}
|
|
21
|
+
function applyUnifiedDiff(original, diff) {
|
|
22
|
+
const lines = original.split("\n");
|
|
23
|
+
for (const line of diff.split("\n")) {
|
|
24
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
25
|
+
const idx = lines.indexOf(line.slice(1));
|
|
26
|
+
if (idx !== -1) lines.splice(idx, 1);
|
|
27
|
+
} else if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
28
|
+
lines.push(line.slice(1));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
async function heal(failedTests, options) {
|
|
34
|
+
const model = getModel(options.ai, "healing");
|
|
35
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const failed of failedTests) {
|
|
38
|
+
if (failed.passed) continue;
|
|
39
|
+
let attempts = 0;
|
|
40
|
+
let healed = false;
|
|
41
|
+
let patchApplied;
|
|
42
|
+
let finalError;
|
|
43
|
+
const original = await fs.readFile(failed.filePath, "utf-8");
|
|
44
|
+
while (attempts < maxAttempts && !healed) {
|
|
45
|
+
attempts++;
|
|
46
|
+
const current = await fs.readFile(failed.filePath, "utf-8");
|
|
47
|
+
const { text: diff } = await generateText({
|
|
48
|
+
model,
|
|
49
|
+
prompt: buildHealPrompt(failed, current),
|
|
50
|
+
maxOutputTokens: 2048
|
|
51
|
+
});
|
|
52
|
+
const patched = applyUnifiedDiff(current, diff);
|
|
53
|
+
try {
|
|
54
|
+
new Function(patched.replace(/^import .+$/gm, ""));
|
|
55
|
+
await fs.writeFile(failed.filePath, patched, "utf-8");
|
|
56
|
+
patchApplied = diff;
|
|
57
|
+
healed = true;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
finalError = String(err);
|
|
60
|
+
await fs.writeFile(failed.filePath, original, "utf-8");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
results.push({ filePath: failed.filePath, healed, attempts, patchApplied, finalError });
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
var index_default = heal;
|
|
68
|
+
export {
|
|
69
|
+
index_default as default,
|
|
70
|
+
heal
|
|
71
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@selfcure/selfcure",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Self-healing loop — sends trace + error to the configured LLM, applies diff, re-validates",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "ricardofrancocustodio",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ricardofrancocustodio/selfcure.git",
|
|
11
|
+
"directory": "packages/selfcure"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/ricardofrancocustodio/selfcure#readme",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": ["dist"],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
28
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
29
|
+
"test": "vitest"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@selfcure/generator": "^0.1.0",
|
|
33
|
+
"@selfcure/runner": "^0.1.0",
|
|
34
|
+
"ai": "^6.0.191",
|
|
35
|
+
"fs-extra": "^11.3.5"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/fs-extra": "^11.0.4"
|
|
39
|
+
}
|
|
40
|
+
}
|