cairn-engine 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.
Files changed (62) hide show
  1. package/README.md +36 -0
  2. package/dist/adapters/context/inline.d.ts +8 -0
  3. package/dist/adapters/context/inline.js +10 -0
  4. package/dist/adapters/context/inline.js.map +1 -0
  5. package/dist/adapters/critics/assertion.d.ts +8 -0
  6. package/dist/adapters/critics/assertion.js +43 -0
  7. package/dist/adapters/critics/assertion.js.map +1 -0
  8. package/dist/adapters/critics/llm.d.ts +10 -0
  9. package/dist/adapters/critics/llm.js +68 -0
  10. package/dist/adapters/critics/llm.js.map +1 -0
  11. package/dist/adapters/drivers/chrome.d.ts +37 -0
  12. package/dist/adapters/drivers/chrome.js +195 -0
  13. package/dist/adapters/drivers/chrome.js.map +1 -0
  14. package/dist/adapters/drivers/fake.d.ts +24 -0
  15. package/dist/adapters/drivers/fake.js +37 -0
  16. package/dist/adapters/drivers/fake.js.map +1 -0
  17. package/dist/adapters/drivers/self-heal.d.ts +34 -0
  18. package/dist/adapters/drivers/self-heal.js +89 -0
  19. package/dist/adapters/drivers/self-heal.js.map +1 -0
  20. package/dist/adapters/llm/anthropic.d.ts +18 -0
  21. package/dist/adapters/llm/anthropic.js +41 -0
  22. package/dist/adapters/llm/anthropic.js.map +1 -0
  23. package/dist/adapters/llm/claude-code.d.ts +14 -0
  24. package/dist/adapters/llm/claude-code.js +48 -0
  25. package/dist/adapters/llm/claude-code.js.map +1 -0
  26. package/dist/adapters/llm/factory.d.ts +8 -0
  27. package/dist/adapters/llm/factory.js +9 -0
  28. package/dist/adapters/llm/factory.js.map +1 -0
  29. package/dist/adapters/planners/static.d.ts +8 -0
  30. package/dist/adapters/planners/static.js +10 -0
  31. package/dist/adapters/planners/static.js.map +1 -0
  32. package/dist/adapters/reporters/console.d.ts +6 -0
  33. package/dist/adapters/reporters/console.js +17 -0
  34. package/dist/adapters/reporters/console.js.map +1 -0
  35. package/dist/adapters/reporters/json.d.ts +7 -0
  36. package/dist/adapters/reporters/json.js +12 -0
  37. package/dist/adapters/reporters/json.js.map +1 -0
  38. package/dist/adapters/skills/file-store.d.ts +11 -0
  39. package/dist/adapters/skills/file-store.js +43 -0
  40. package/dist/adapters/skills/file-store.js.map +1 -0
  41. package/dist/cli.d.ts +2 -0
  42. package/dist/cli.js +158 -0
  43. package/dist/cli.js.map +1 -0
  44. package/dist/core/discover.d.ts +25 -0
  45. package/dist/core/discover.js +93 -0
  46. package/dist/core/discover.js.map +1 -0
  47. package/dist/core/pipeline.d.ts +8 -0
  48. package/dist/core/pipeline.js +49 -0
  49. package/dist/core/pipeline.js.map +1 -0
  50. package/dist/core/ports.d.ts +56 -0
  51. package/dist/core/ports.js +2 -0
  52. package/dist/core/ports.js.map +1 -0
  53. package/dist/core/types.d.ts +102 -0
  54. package/dist/core/types.js +3 -0
  55. package/dist/core/types.js.map +1 -0
  56. package/dist/index.d.ts +22 -0
  57. package/dist/index.js +20 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/run.d.ts +25 -0
  60. package/dist/run.js +53 -0
  61. package/dist/run.js.map +1 -0
  62. package/package.json +57 -0
@@ -0,0 +1,49 @@
1
+ async function executeStep(driver, step) {
2
+ try {
3
+ switch (step.kind) {
4
+ case "goto":
5
+ await driver.goto(step.url);
6
+ break;
7
+ case "click":
8
+ await driver.click(step.target);
9
+ break;
10
+ case "type":
11
+ await driver.type(step.target, step.text);
12
+ break;
13
+ }
14
+ return { step, ok: true };
15
+ }
16
+ catch (err) {
17
+ return { step, ok: false, error: err instanceof Error ? err.message : String(err) };
18
+ }
19
+ }
20
+ export async function runHarness(harness, task) {
21
+ const { context, planner, driver, critic, reporter } = harness;
22
+ const ctx = await context.provide(task);
23
+ const scenario = await planner.plan(ctx);
24
+ // Drive steps; stop on the first failure but still observe the resulting state.
25
+ const actions = [];
26
+ try {
27
+ for (const step of scenario.steps) {
28
+ const result = await executeStep(driver, step);
29
+ actions.push(result);
30
+ if (!result.ok)
31
+ break;
32
+ }
33
+ // Auto-wait for network idle so evidence captures late subresources, not a race (design §3).
34
+ await driver.settle();
35
+ const observed = await driver.observe();
36
+ const evidence = {
37
+ ...observed,
38
+ execution: { ...observed.execution, actions, blocked: actions.some((a) => !a.ok) },
39
+ };
40
+ const verdict = await critic.judge(evidence, scenario.assertions);
41
+ const out = { scenario: scenario.name, context: ctx, evidence, verdict };
42
+ await reporter.emit(out);
43
+ return out;
44
+ }
45
+ finally {
46
+ await driver.close();
47
+ }
48
+ }
49
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAQA,KAAK,UAAU,WAAW,CAAC,MAAyB,EAAE,IAAU;IAC9D,IAAI,CAAC;QACH,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,MAAM;gBACT,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC5B,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAChC,MAAM;YACR,KAAK,MAAM;gBACT,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC1C,MAAM;QACV,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACtF,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB,EAAE,IAAY;IAC7D,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAE/D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEzC,gFAAgF;IAChF,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,MAAM;QACxB,CAAC;QAED,6FAA6F;QAC7F,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QAEtB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAa;YACzB,GAAG,QAAQ;YACX,SAAS,EAAE,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;SACnF,CAAC;QAEF,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAClE,MAAM,GAAG,GAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACjF,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,GAAG,CAAC;IACb,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The ports of the cairn engine (invariant #2: add behavior by implementing one of these,
3
+ * never by branching inside a stage). Core depends only on these; `../adapters` implement them.
4
+ */
5
+ import type { Assertion, Context, Evidence, PageElement, Result, Scenario, SettleOptions, Target, Verdict } from "./types.js";
6
+ /** Grounding from any source (NL, git diff, ticket, RAG). */
7
+ export interface ContextProvider {
8
+ provide(task: string): Promise<Context>;
9
+ }
10
+ /** Intent → ordered Scenario. Frozen-replay planning uses no LLM (invariant #4); discovery is a separate LLM planner. */
11
+ export interface Planner {
12
+ plan(ctx: Context): Promise<Scenario>;
13
+ }
14
+ /** Drives a browser. Replaceable without touching core (invariant #5); resolves targets from intent, not handles. */
15
+ export interface Driver {
16
+ goto(url: string): Promise<void>;
17
+ click(target: Target): Promise<void>;
18
+ type(target: Target, text: string): Promise<void>;
19
+ snapshot(): Promise<PageElement[]>;
20
+ /** Execute-stage auto-wait for network idle (design §3). Best-effort, time-bounded, never throws. */
21
+ settle(options?: SettleOptions): Promise<void>;
22
+ observe(): Promise<Evidence>;
23
+ close(): Promise<void>;
24
+ }
25
+ export interface Skill {
26
+ name: string;
27
+ scenario: Scenario;
28
+ }
29
+ export interface SkillStore {
30
+ resolve(name: string): Promise<Skill | undefined>;
31
+ }
32
+ /** Judges evidence against assertions (mechanical, baseline, or LLM). */
33
+ export interface Critic {
34
+ judge(evidence: Evidence, assertions: Assertion[]): Promise<Verdict>;
35
+ }
36
+ /** Emits a result anywhere — console, json, an arbitrary tracker. */
37
+ export interface Reporter {
38
+ emit(result: Result): Promise<void>;
39
+ }
40
+ export interface Harness {
41
+ context: ContextProvider;
42
+ planner: Planner;
43
+ driver: Driver;
44
+ critic: Critic;
45
+ reporter: Reporter;
46
+ skills?: SkillStore;
47
+ }
48
+ /** Model-agnostic LLM seam (invariant #5); `createLlmClient` picks the implementation. */
49
+ export interface LlmClient {
50
+ readonly id: string;
51
+ complete(prompt: string, opts?: CompleteOptions): Promise<string>;
52
+ }
53
+ export interface CompleteOptions {
54
+ system?: string;
55
+ maxTokens?: number;
56
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/core/ports.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ /** Domain types for the pipeline (Context → Plan → Execute → Judge → Report). App-agnostic by invariant #1. */
2
+ export interface Context {
3
+ intent: string;
4
+ baseUrl?: string;
5
+ notes?: string[];
6
+ }
7
+ export type Step = {
8
+ kind: "goto";
9
+ url: string;
10
+ } | {
11
+ kind: "click";
12
+ target: Target;
13
+ } | {
14
+ kind: "type";
15
+ target: Target;
16
+ text: string;
17
+ };
18
+ /** Locate an element by intent, not a driver handle: `text` = accessible name, `selector` = CSS fallback. */
19
+ export interface Target {
20
+ text?: string;
21
+ selector?: string;
22
+ }
23
+ /**
24
+ * `expect` is the only kind an LLM judges; a scenario with only mechanical kinds replays
25
+ * deterministically (invariant #4).
26
+ */
27
+ export type Assertion = {
28
+ kind: "navigated";
29
+ to?: string;
30
+ } | {
31
+ kind: "no-console-errors";
32
+ } | {
33
+ kind: "no-failed-requests";
34
+ } | {
35
+ kind: "request-status";
36
+ urlIncludes: string;
37
+ status: number;
38
+ } | {
39
+ kind: "expect";
40
+ criterion: string;
41
+ };
42
+ export interface Scenario {
43
+ name: string;
44
+ steps: Step[];
45
+ assertions: Assertion[];
46
+ }
47
+ /** An interactive element the discover loop perceives and acts on. */
48
+ export interface PageElement {
49
+ role: string;
50
+ name: string;
51
+ }
52
+ export interface SettleOptions {
53
+ idleMs?: number;
54
+ timeoutMs?: number;
55
+ pollMs?: number;
56
+ }
57
+ export interface NetworkRequest {
58
+ method: string;
59
+ url: string;
60
+ status: number;
61
+ resourceType?: string;
62
+ }
63
+ /** Three observable layers the Critic judges on — never "the screen looked right". */
64
+ export interface Evidence {
65
+ execution: {
66
+ actions: ExecutedAction[];
67
+ navigated: boolean;
68
+ finalUrl?: string;
69
+ blocked: boolean;
70
+ };
71
+ perception: {
72
+ screenshot?: string;
73
+ };
74
+ logic: {
75
+ requests: NetworkRequest[];
76
+ console: ConsoleMessage[];
77
+ };
78
+ }
79
+ export interface ExecutedAction {
80
+ step: Step;
81
+ ok: boolean;
82
+ error?: string;
83
+ }
84
+ export interface ConsoleMessage {
85
+ type: string;
86
+ text: string;
87
+ }
88
+ export interface AssertionResult {
89
+ assertion: Assertion;
90
+ passed: boolean;
91
+ detail?: string;
92
+ }
93
+ export interface Verdict {
94
+ passed: boolean;
95
+ results: AssertionResult[];
96
+ }
97
+ export interface Result {
98
+ scenario: string;
99
+ context: Context;
100
+ evidence: Evidence;
101
+ verdict: Verdict;
102
+ }
@@ -0,0 +1,3 @@
1
+ /** Domain types for the pipeline (Context → Plan → Execute → Judge → Report). App-agnostic by invariant #1. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,+GAA+G"}
@@ -0,0 +1,22 @@
1
+ /** Public surface of cairn-engine. */
2
+ export * from "./core/types.js";
3
+ export * from "./core/ports.js";
4
+ export { runHarness } from "./core/pipeline.js";
5
+ export { runScenario, needsLlmCritic, applyHeals } from "./run.js";
6
+ export type { RunScenarioOptions, RunScenarioResult } from "./run.js";
7
+ export { InlineContextProvider } from "./adapters/context/inline.js";
8
+ export { StaticPlanner } from "./adapters/planners/static.js";
9
+ export { AssertionCritic, checkAssertion } from "./adapters/critics/assertion.js";
10
+ export { LlmCritic, summarizeEvidence } from "./adapters/critics/llm.js";
11
+ export { ConsoleReporter } from "./adapters/reporters/console.js";
12
+ export { JsonReporter } from "./adapters/reporters/json.js";
13
+ export { FakeDriver } from "./adapters/drivers/fake.js";
14
+ export { ChromeDevToolsDriver } from "./adapters/drivers/chrome.js";
15
+ export { SelfHealingDriver, parseHealChoice } from "./adapters/drivers/self-heal.js";
16
+ export type { Heal, SelfHealOptions } from "./adapters/drivers/self-heal.js";
17
+ export { ClaudeCodeLlmClient } from "./adapters/llm/claude-code.js";
18
+ export { AnthropicLlmClient } from "./adapters/llm/anthropic.js";
19
+ export { createLlmClient } from "./adapters/llm/factory.js";
20
+ export { FileSkillStore, loadSkillFile } from "./adapters/skills/file-store.js";
21
+ export { discover, parseDecision } from "./core/discover.js";
22
+ export type { DiscoverOptions, Decision } from "./core/discover.js";
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /** Public surface of cairn-engine. */
2
+ export * from "./core/types.js";
3
+ export * from "./core/ports.js";
4
+ export { runHarness } from "./core/pipeline.js";
5
+ export { runScenario, needsLlmCritic, applyHeals } from "./run.js";
6
+ export { InlineContextProvider } from "./adapters/context/inline.js";
7
+ export { StaticPlanner } from "./adapters/planners/static.js";
8
+ export { AssertionCritic, checkAssertion } from "./adapters/critics/assertion.js";
9
+ export { LlmCritic, summarizeEvidence } from "./adapters/critics/llm.js";
10
+ export { ConsoleReporter } from "./adapters/reporters/console.js";
11
+ export { JsonReporter } from "./adapters/reporters/json.js";
12
+ export { FakeDriver } from "./adapters/drivers/fake.js";
13
+ export { ChromeDevToolsDriver } from "./adapters/drivers/chrome.js";
14
+ export { SelfHealingDriver, parseHealChoice } from "./adapters/drivers/self-heal.js";
15
+ export { ClaudeCodeLlmClient } from "./adapters/llm/claude-code.js";
16
+ export { AnthropicLlmClient } from "./adapters/llm/anthropic.js";
17
+ export { createLlmClient } from "./adapters/llm/factory.js";
18
+ export { FileSkillStore, loadSkillFile } from "./adapters/skills/file-store.js";
19
+ export { discover, parseDecision } from "./core/discover.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhF,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { ContextProvider, Critic, Driver, LlmClient, Reporter } from "./core/ports.js";
2
+ import type { Heal } from "./adapters/drivers/self-heal.js";
3
+ import type { Result, Scenario } from "./core/types.js";
4
+ export interface RunScenarioOptions {
5
+ driver?: Driver;
6
+ /** Default: LlmCritic if the scenario has `expect`, else AssertionCritic. */
7
+ critic?: Critic;
8
+ context?: ContextProvider;
9
+ reporter?: Reporter;
10
+ llm?: LlmClient;
11
+ /** Wrap the driver so broken steps are repaired by the LLM and retried. */
12
+ heal?: boolean;
13
+ model?: string;
14
+ }
15
+ export interface RunScenarioResult {
16
+ result: Result;
17
+ /** Substitutions self-heal made (empty unless `heal` was set and a step broke). */
18
+ heals: Heal[];
19
+ /** Scenario rewritten with healed targets, ready to re-freeze. Undefined if no heals. */
20
+ healedScenario?: Scenario;
21
+ }
22
+ export declare function needsLlmCritic(scenario: Scenario): boolean;
23
+ /** Rewrite a scenario's targets with the names self-heal substituted, for re-freezing. */
24
+ export declare function applyHeals(scenario: Scenario, heals: Heal[]): Scenario;
25
+ export declare function runScenario(scenario: Scenario, opts?: RunScenarioOptions): Promise<RunScenarioResult>;
package/dist/run.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * High-level entry point: run/replay a Scenario with sensible defaults (the CLI, a desktop
3
+ * app, or CI all go through here). No LLM is constructed unless an `expect` critic or
4
+ * `heal` needs one, so a plain mechanical replay stays deterministic (invariant #4).
5
+ */
6
+ import { runHarness } from "./core/pipeline.js";
7
+ import { InlineContextProvider } from "./adapters/context/inline.js";
8
+ import { StaticPlanner } from "./adapters/planners/static.js";
9
+ import { AssertionCritic } from "./adapters/critics/assertion.js";
10
+ import { LlmCritic } from "./adapters/critics/llm.js";
11
+ import { ChromeDevToolsDriver } from "./adapters/drivers/chrome.js";
12
+ import { SelfHealingDriver } from "./adapters/drivers/self-heal.js";
13
+ import { ConsoleReporter } from "./adapters/reporters/console.js";
14
+ import { createLlmClient } from "./adapters/llm/factory.js";
15
+ export function needsLlmCritic(scenario) {
16
+ return scenario.assertions.some((a) => a.kind === "expect");
17
+ }
18
+ /** Rewrite a scenario's targets with the names self-heal substituted, for re-freezing. */
19
+ export function applyHeals(scenario, heals) {
20
+ if (!heals.length)
21
+ return scenario;
22
+ const byOriginal = new Map(heals.map((h) => [h.original.text, h.healedText]));
23
+ return {
24
+ ...scenario,
25
+ steps: scenario.steps.map((step) => {
26
+ if ((step.kind === "click" || step.kind === "type") && step.target.text) {
27
+ const healed = byOriginal.get(step.target.text);
28
+ if (healed)
29
+ return { ...step, target: { ...step.target, text: healed } };
30
+ }
31
+ return step;
32
+ }),
33
+ };
34
+ }
35
+ export async function runScenario(scenario, opts = {}) {
36
+ // Build the LLM lazily and once — only if the critic or heal needs it.
37
+ let llmCache = opts.llm;
38
+ const getLlm = () => (llmCache ??= createLlmClient(opts.model ? { model: opts.model } : {}));
39
+ const critic = opts.critic ?? (needsLlmCritic(scenario) ? new LlmCritic(getLlm()) : new AssertionCritic());
40
+ const baseDriver = opts.driver ?? new ChromeDevToolsDriver();
41
+ let healer;
42
+ const driver = opts.heal ? (healer = new SelfHealingDriver(baseDriver, getLlm())) : baseDriver;
43
+ const result = await runHarness({
44
+ context: opts.context ?? new InlineContextProvider(),
45
+ planner: new StaticPlanner(scenario),
46
+ driver,
47
+ critic,
48
+ reporter: opts.reporter ?? new ConsoleReporter(),
49
+ }, scenario.name);
50
+ const heals = healer?.heals ?? [];
51
+ return { result, heals, healedScenario: heals.length ? applyHeals(scenario, heals) : undefined };
52
+ }
53
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "cairn-engine",
3
+ "version": "0.1.0",
4
+ "description": "An engine for self-healing E2E browser tests — discovered once by an AI, replayed deterministically.",
5
+ "keywords": [
6
+ "e2e",
7
+ "testing",
8
+ "browser",
9
+ "qa",
10
+ "self-healing",
11
+ "deterministic",
12
+ "llm",
13
+ "ai",
14
+ "chrome-devtools",
15
+ "mcp",
16
+ "automation"
17
+ ],
18
+ "license": "MIT",
19
+ "author": "team-poem (https://github.com/team-poem)",
20
+ "homepage": "https://github.com/team-poem/cairn#readme",
21
+ "bugs": "https://github.com/team-poem/cairn/issues",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/team-poem/cairn.git",
25
+ "directory": "packages/harness"
26
+ },
27
+ "type": "module",
28
+ "bin": {
29
+ "cairn": "./dist/cli.js"
30
+ },
31
+ "exports": {
32
+ ".": "./dist/index.js"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md"
37
+ ],
38
+ "engines": {
39
+ "node": ">=20"
40
+ },
41
+ "scripts": {
42
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit",
44
+ "test": "vitest run",
45
+ "dev": "tsx src/cli.ts",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.0.0",
53
+ "tsx": "^4.19.0",
54
+ "typescript": "^5.6.0",
55
+ "vitest": "^2.1.0"
56
+ }
57
+ }