ralph-research 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 (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/adapters/extractor/command-extractor.d.ts +9 -0
  4. package/dist/adapters/extractor/command-extractor.js +93 -0
  5. package/dist/adapters/extractor/command-extractor.js.map +1 -0
  6. package/dist/adapters/extractor/llm-judge-extractor.d.ts +9 -0
  7. package/dist/adapters/extractor/llm-judge-extractor.js +12 -0
  8. package/dist/adapters/extractor/llm-judge-extractor.js.map +1 -0
  9. package/dist/adapters/fs/json-file-decision-store.d.ts +10 -0
  10. package/dist/adapters/fs/json-file-decision-store.js +53 -0
  11. package/dist/adapters/fs/json-file-decision-store.js.map +1 -0
  12. package/dist/adapters/fs/json-file-frontier-store.d.ts +8 -0
  13. package/dist/adapters/fs/json-file-frontier-store.js +29 -0
  14. package/dist/adapters/fs/json-file-frontier-store.js.map +1 -0
  15. package/dist/adapters/fs/json-file-run-store.d.ts +10 -0
  16. package/dist/adapters/fs/json-file-run-store.js +53 -0
  17. package/dist/adapters/fs/json-file-run-store.js.map +1 -0
  18. package/dist/adapters/fs/lockfile.d.ts +24 -0
  19. package/dist/adapters/fs/lockfile.js +110 -0
  20. package/dist/adapters/fs/lockfile.js.map +1 -0
  21. package/dist/adapters/fs/manifest-loader.d.ts +10 -0
  22. package/dist/adapters/fs/manifest-loader.js +43 -0
  23. package/dist/adapters/fs/manifest-loader.js.map +1 -0
  24. package/dist/adapters/git/git-client.d.ts +9 -0
  25. package/dist/adapters/git/git-client.js +23 -0
  26. package/dist/adapters/git/git-client.js.map +1 -0
  27. package/dist/adapters/index.d.ts +1 -0
  28. package/dist/adapters/index.js +3 -0
  29. package/dist/adapters/index.js.map +1 -0
  30. package/dist/adapters/judge/llm-judge-provider.d.ts +33 -0
  31. package/dist/adapters/judge/llm-judge-provider.js +90 -0
  32. package/dist/adapters/judge/llm-judge-provider.js.map +1 -0
  33. package/dist/adapters/proposer/command-proposer.d.ts +15 -0
  34. package/dist/adapters/proposer/command-proposer.js +29 -0
  35. package/dist/adapters/proposer/command-proposer.js.map +1 -0
  36. package/dist/app/context.d.ts +5 -0
  37. package/dist/app/context.js +7 -0
  38. package/dist/app/context.js.map +1 -0
  39. package/dist/app/services/manual-decision-service.d.ts +20 -0
  40. package/dist/app/services/manual-decision-service.js +143 -0
  41. package/dist/app/services/manual-decision-service.js.map +1 -0
  42. package/dist/app/services/project-state-service.d.ts +52 -0
  43. package/dist/app/services/project-state-service.js +92 -0
  44. package/dist/app/services/project-state-service.js.map +1 -0
  45. package/dist/app/services/run-cycle-service.d.ts +25 -0
  46. package/dist/app/services/run-cycle-service.js +69 -0
  47. package/dist/app/services/run-cycle-service.js.map +1 -0
  48. package/dist/cli/commands/accept.d.ts +10 -0
  49. package/dist/cli/commands/accept.js +54 -0
  50. package/dist/cli/commands/accept.js.map +1 -0
  51. package/dist/cli/commands/demo.d.ts +9 -0
  52. package/dist/cli/commands/demo.js +108 -0
  53. package/dist/cli/commands/demo.js.map +1 -0
  54. package/dist/cli/commands/frontier.d.ts +8 -0
  55. package/dist/cli/commands/frontier.js +48 -0
  56. package/dist/cli/commands/frontier.js.map +1 -0
  57. package/dist/cli/commands/init.d.ts +10 -0
  58. package/dist/cli/commands/init.js +123 -0
  59. package/dist/cli/commands/init.js.map +1 -0
  60. package/dist/cli/commands/inspect.d.ts +8 -0
  61. package/dist/cli/commands/inspect.js +55 -0
  62. package/dist/cli/commands/inspect.js.map +1 -0
  63. package/dist/cli/commands/reject.d.ts +10 -0
  64. package/dist/cli/commands/reject.js +54 -0
  65. package/dist/cli/commands/reject.js.map +1 -0
  66. package/dist/cli/commands/run.d.ts +13 -0
  67. package/dist/cli/commands/run.js +71 -0
  68. package/dist/cli/commands/run.js.map +1 -0
  69. package/dist/cli/commands/serve-mcp.d.ts +7 -0
  70. package/dist/cli/commands/serve-mcp.js +32 -0
  71. package/dist/cli/commands/serve-mcp.js.map +1 -0
  72. package/dist/cli/commands/status.d.ts +8 -0
  73. package/dist/cli/commands/status.js +53 -0
  74. package/dist/cli/commands/status.js.map +1 -0
  75. package/dist/cli/commands/validate.d.ts +11 -0
  76. package/dist/cli/commands/validate.js +56 -0
  77. package/dist/cli/commands/validate.js.map +1 -0
  78. package/dist/cli/main.d.ts +2 -0
  79. package/dist/cli/main.js +38 -0
  80. package/dist/cli/main.js.map +1 -0
  81. package/dist/core/engine/anchor-checker.d.ts +35 -0
  82. package/dist/core/engine/anchor-checker.js +84 -0
  83. package/dist/core/engine/anchor-checker.js.map +1 -0
  84. package/dist/core/engine/audit-sampler.d.ts +16 -0
  85. package/dist/core/engine/audit-sampler.js +25 -0
  86. package/dist/core/engine/audit-sampler.js.map +1 -0
  87. package/dist/core/engine/change-budget.d.ts +11 -0
  88. package/dist/core/engine/change-budget.js +10 -0
  89. package/dist/core/engine/change-budget.js.map +1 -0
  90. package/dist/core/engine/cycle-runner.d.ts +39 -0
  91. package/dist/core/engine/cycle-runner.js +652 -0
  92. package/dist/core/engine/cycle-runner.js.map +1 -0
  93. package/dist/core/engine/experiment-runner.d.ts +13 -0
  94. package/dist/core/engine/experiment-runner.js +24 -0
  95. package/dist/core/engine/experiment-runner.js.map +1 -0
  96. package/dist/core/engine/history-compactor.d.ts +15 -0
  97. package/dist/core/engine/history-compactor.js +76 -0
  98. package/dist/core/engine/history-compactor.js.map +1 -0
  99. package/dist/core/engine/judge-pack.d.ts +44 -0
  100. package/dist/core/engine/judge-pack.js +111 -0
  101. package/dist/core/engine/judge-pack.js.map +1 -0
  102. package/dist/core/engine/parallel-proposer.d.ts +21 -0
  103. package/dist/core/engine/parallel-proposer.js +58 -0
  104. package/dist/core/engine/parallel-proposer.js.map +1 -0
  105. package/dist/core/engine/scope-checker.d.ts +35 -0
  106. package/dist/core/engine/scope-checker.js +166 -0
  107. package/dist/core/engine/scope-checker.js.map +1 -0
  108. package/dist/core/engine/workspace-manager.d.ts +32 -0
  109. package/dist/core/engine/workspace-manager.js +145 -0
  110. package/dist/core/engine/workspace-manager.js.map +1 -0
  111. package/dist/core/index.d.ts +1 -0
  112. package/dist/core/index.js +3 -0
  113. package/dist/core/index.js.map +1 -0
  114. package/dist/core/manifest/defaults.d.ts +55 -0
  115. package/dist/core/manifest/defaults.js +56 -0
  116. package/dist/core/manifest/defaults.js.map +1 -0
  117. package/dist/core/manifest/schema.d.ts +647 -0
  118. package/dist/core/manifest/schema.js +254 -0
  119. package/dist/core/manifest/schema.js.map +1 -0
  120. package/dist/core/model/decision-record.d.ts +38 -0
  121. package/dist/core/model/decision-record.js +29 -0
  122. package/dist/core/model/decision-record.js.map +1 -0
  123. package/dist/core/model/frontier-entry.d.ts +24 -0
  124. package/dist/core/model/frontier-entry.js +15 -0
  125. package/dist/core/model/frontier-entry.js.map +1 -0
  126. package/dist/core/model/metric.d.ts +13 -0
  127. package/dist/core/model/metric.js +10 -0
  128. package/dist/core/model/metric.js.map +1 -0
  129. package/dist/core/model/run-record.d.ts +110 -0
  130. package/dist/core/model/run-record.js +104 -0
  131. package/dist/core/model/run-record.js.map +1 -0
  132. package/dist/core/ports/decision-store.d.ts +6 -0
  133. package/dist/core/ports/decision-store.js +2 -0
  134. package/dist/core/ports/decision-store.js.map +1 -0
  135. package/dist/core/ports/frontier-store.d.ts +5 -0
  136. package/dist/core/ports/frontier-store.js +2 -0
  137. package/dist/core/ports/frontier-store.js.map +1 -0
  138. package/dist/core/ports/run-store.d.ts +6 -0
  139. package/dist/core/ports/run-store.js +2 -0
  140. package/dist/core/ports/run-store.js.map +1 -0
  141. package/dist/core/state/constraint-engine.d.ts +18 -0
  142. package/dist/core/state/constraint-engine.js +42 -0
  143. package/dist/core/state/constraint-engine.js.map +1 -0
  144. package/dist/core/state/frontier-engine.d.ts +24 -0
  145. package/dist/core/state/frontier-engine.js +178 -0
  146. package/dist/core/state/frontier-engine.js.map +1 -0
  147. package/dist/core/state/ratchet-engine.d.ts +28 -0
  148. package/dist/core/state/ratchet-engine.js +177 -0
  149. package/dist/core/state/ratchet-engine.js.map +1 -0
  150. package/dist/core/state/run-state-machine.d.ts +17 -0
  151. package/dist/core/state/run-state-machine.js +94 -0
  152. package/dist/core/state/run-state-machine.js.map +1 -0
  153. package/dist/mcp/main.d.ts +1 -0
  154. package/dist/mcp/main.js +8 -0
  155. package/dist/mcp/main.js.map +1 -0
  156. package/dist/mcp/server.d.ts +6 -0
  157. package/dist/mcp/server.js +97 -0
  158. package/dist/mcp/server.js.map +1 -0
  159. package/dist/shared/fs-errors.d.ts +1 -0
  160. package/dist/shared/fs-errors.js +4 -0
  161. package/dist/shared/fs-errors.js.map +1 -0
  162. package/dist/shared/logger.d.ts +2 -0
  163. package/dist/shared/logger.js +5 -0
  164. package/dist/shared/logger.js.map +1 -0
  165. package/dist/shared/template-utils.d.ts +9 -0
  166. package/dist/shared/template-utils.js +50 -0
  167. package/dist/shared/template-utils.js.map +1 -0
  168. package/package.json +44 -0
  169. package/templates/writing/docs/draft.md +1 -0
  170. package/templates/writing/prompts/judge.md +15 -0
  171. package/templates/writing/ralph.yaml +63 -0
  172. package/templates/writing/scripts/experiment.mjs +6 -0
  173. package/templates/writing/scripts/metric.mjs +24 -0
  174. package/templates/writing/scripts/propose.mjs +13 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=decision-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-store.js","sourceRoot":"","sources":["../../../src/core/ports/decision-store.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import type { FrontierEntry } from "../model/frontier-entry.js";
2
+ export interface FrontierStore {
3
+ save(entries: FrontierEntry[]): Promise<void>;
4
+ load(): Promise<FrontierEntry[]>;
5
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=frontier-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontier-store.js","sourceRoot":"","sources":["../../../src/core/ports/frontier-store.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { RunRecord } from "../model/run-record.js";
2
+ export interface RunStore {
3
+ put(record: RunRecord): Promise<void>;
4
+ get(runId: string): Promise<RunRecord | null>;
5
+ list(): Promise<RunRecord[]>;
6
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-store.js","sourceRoot":"","sources":["../../../src/core/ports/run-store.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import type { z } from "zod";
2
+ import { constraintSchema } from "../manifest/schema.js";
3
+ import type { MetricResult } from "../model/metric.js";
4
+ export type ConstraintDefinition = z.infer<typeof constraintSchema>;
5
+ export interface ConstraintEvaluation {
6
+ metric: string;
7
+ passed: boolean;
8
+ actual: number;
9
+ expected: number;
10
+ op: ConstraintDefinition["op"];
11
+ reason: string;
12
+ }
13
+ export interface ConstraintEvaluationSummary {
14
+ passed: boolean;
15
+ results: ConstraintEvaluation[];
16
+ reason: string;
17
+ }
18
+ export declare function evaluateConstraints(constraints: ConstraintDefinition[], metrics: Record<string, MetricResult>): ConstraintEvaluationSummary;
@@ -0,0 +1,42 @@
1
+ export function evaluateConstraints(constraints, metrics) {
2
+ const results = constraints.map((constraint) => {
3
+ const metric = metrics[constraint.metric];
4
+ if (!metric) {
5
+ throw new Error(`Missing metric result for constraint "${constraint.metric}"`);
6
+ }
7
+ const passed = compare(metric.value, constraint.op, constraint.value);
8
+ return {
9
+ metric: constraint.metric,
10
+ passed,
11
+ actual: metric.value,
12
+ expected: constraint.value,
13
+ op: constraint.op,
14
+ reason: passed
15
+ ? `constraint ${constraint.metric} satisfied: ${metric.value} ${constraint.op} ${constraint.value}`
16
+ : `constraint ${constraint.metric} failed: ${metric.value} ${constraint.op} ${constraint.value}`,
17
+ };
18
+ });
19
+ const failed = results.filter((result) => !result.passed);
20
+ return {
21
+ passed: failed.length === 0,
22
+ results,
23
+ reason: failed.length === 0
24
+ ? "all constraints satisfied"
25
+ : failed.map((result) => result.reason).join("; "),
26
+ };
27
+ }
28
+ function compare(actual, op, expected) {
29
+ switch (op) {
30
+ case ">=":
31
+ return actual >= expected;
32
+ case ">":
33
+ return actual > expected;
34
+ case "<=":
35
+ return actual <= expected;
36
+ case "<":
37
+ return actual < expected;
38
+ case "==":
39
+ return actual === expected;
40
+ }
41
+ }
42
+ //# sourceMappingURL=constraint-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraint-engine.js","sourceRoot":"","sources":["../../../src/core/state/constraint-engine.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,mBAAmB,CACjC,WAAmC,EACnC,OAAqC;IAErC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,yCAAyC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO;YACL,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,MAAM;YACN,MAAM,EAAE,MAAM,CAAC,KAAK;YACpB,QAAQ,EAAE,UAAU,CAAC,KAAK;YAC1B,EAAE,EAAE,UAAU,CAAC,EAAE;YACjB,MAAM,EAAE,MAAM;gBACZ,CAAC,CAAC,cAAc,UAAU,CAAC,MAAM,eAAe,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC,EAAE,IAAI,UAAU,CAAC,KAAK,EAAE;gBACnG,CAAC,CAAC,cAAc,UAAU,CAAC,MAAM,YAAY,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC,EAAE,IAAI,UAAU,CAAC,KAAK,EAAE;SACnG,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;QAC3B,OAAO;QACP,MAAM,EACJ,MAAM,CAAC,MAAM,KAAK,CAAC;YACjB,CAAC,CAAC,2BAA2B;YAC7B,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;KACvD,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,MAAc,EAAE,EAA8B,EAAE,QAAgB;IAC/E,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,IAAI;YACP,OAAO,MAAM,IAAI,QAAQ,CAAC;QAC5B,KAAK,GAAG;YACN,OAAO,MAAM,GAAG,QAAQ,CAAC;QAC3B,KAAK,IAAI;YACP,OAAO,MAAM,IAAI,QAAQ,CAAC;QAC5B,KAAK,GAAG;YACN,OAAO,MAAM,GAAG,QAAQ,CAAC;QAC3B,KAAK,IAAI;YACP,OAAO,MAAM,KAAK,QAAQ,CAAC;IAC/B,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { FrontierEntry } from "../model/frontier-entry.js";
2
+ import type { ParetoObjectiveConfig } from "../manifest/schema.js";
3
+ import type { MetricResult } from "../model/metric.js";
4
+ export interface FrontierComparison {
5
+ outcome: "accepted" | "rejected";
6
+ frontierChanged: boolean;
7
+ reason: string;
8
+ incumbent?: FrontierEntry;
9
+ delta?: number;
10
+ }
11
+ export interface FrontierUpdate {
12
+ entries: FrontierEntry[];
13
+ comparison: FrontierComparison;
14
+ }
15
+ export interface ParetoFrontierUpdate {
16
+ entries: FrontierEntry[];
17
+ comparison: FrontierComparison;
18
+ }
19
+ export declare function compareSingleBestFrontier(currentFrontier: FrontierEntry[], candidateEntry: FrontierEntry, primaryMetric: string): FrontierComparison;
20
+ export declare function updateSingleBestFrontier(currentFrontier: FrontierEntry[], candidateEntry: FrontierEntry, primaryMetric: string): FrontierUpdate;
21
+ export declare function compareParetoFrontier(currentFrontier: FrontierEntry[], candidateEntry: FrontierEntry, objectives: ParetoObjectiveConfig[]): FrontierComparison;
22
+ export declare function updateParetoFrontier(currentFrontier: FrontierEntry[], candidateEntry: FrontierEntry, objectives: ParetoObjectiveConfig[], tieBreaker?: "hypervolume" | "none", referencePoint?: Record<string, number>): ParetoFrontierUpdate;
23
+ export declare function directionalDelta(candidate: MetricResult, incumbent: MetricResult): number;
24
+ export declare function buildMetricComparisonReason(metricId: string, candidate: MetricResult, incumbent: MetricResult, delta: number, verdict: "improved" | "not_improved"): string;
@@ -0,0 +1,178 @@
1
+ export function compareSingleBestFrontier(currentFrontier, candidateEntry, primaryMetric) {
2
+ assertSingleBestShape(currentFrontier);
3
+ if (currentFrontier.length === 0) {
4
+ return {
5
+ outcome: "accepted",
6
+ frontierChanged: true,
7
+ reason: `frontier empty; candidate becomes best entry on metric ${primaryMetric}`,
8
+ };
9
+ }
10
+ const incumbent = currentFrontier[0];
11
+ if (!incumbent) {
12
+ throw new Error("single_best frontier invariant violated: incumbent missing");
13
+ }
14
+ const candidateMetric = getMetric(candidateEntry, primaryMetric);
15
+ const incumbentMetric = getMetric(incumbent, primaryMetric);
16
+ const delta = directionalDelta(candidateMetric, incumbentMetric);
17
+ if (delta > 0) {
18
+ return {
19
+ outcome: "accepted",
20
+ frontierChanged: true,
21
+ incumbent,
22
+ delta,
23
+ reason: buildMetricComparisonReason(primaryMetric, candidateMetric, incumbentMetric, delta, "improved"),
24
+ };
25
+ }
26
+ return {
27
+ outcome: "rejected",
28
+ frontierChanged: false,
29
+ incumbent,
30
+ delta,
31
+ reason: buildMetricComparisonReason(primaryMetric, candidateMetric, incumbentMetric, delta, "not_improved"),
32
+ };
33
+ }
34
+ export function updateSingleBestFrontier(currentFrontier, candidateEntry, primaryMetric) {
35
+ const comparison = compareSingleBestFrontier(currentFrontier, candidateEntry, primaryMetric);
36
+ return {
37
+ entries: comparison.frontierChanged ? [candidateEntry] : currentFrontier,
38
+ comparison,
39
+ };
40
+ }
41
+ export function compareParetoFrontier(currentFrontier, candidateEntry, objectives) {
42
+ if (currentFrontier.length === 0) {
43
+ return {
44
+ outcome: "accepted",
45
+ frontierChanged: true,
46
+ reason: `frontier empty; candidate added to pareto frontier on objectives ${describeObjectives(objectives)}`,
47
+ };
48
+ }
49
+ const dominatingIncumbent = currentFrontier.find((entry) => dominates(entry, candidateEntry, objectives));
50
+ if (dominatingIncumbent) {
51
+ return {
52
+ outcome: "rejected",
53
+ frontierChanged: false,
54
+ incumbent: dominatingIncumbent,
55
+ reason: `candidate is pareto-dominated by ${dominatingIncumbent.frontierId} on objectives ${describeObjectives(objectives)}`,
56
+ };
57
+ }
58
+ const dominatedEntries = currentFrontier.filter((entry) => dominates(candidateEntry, entry, objectives));
59
+ if (dominatedEntries.length > 0) {
60
+ return {
61
+ outcome: "accepted",
62
+ frontierChanged: true,
63
+ reason: `candidate dominates ${dominatedEntries.length} frontier entr${dominatedEntries.length === 1 ? "y" : "ies"} on objectives ${describeObjectives(objectives)}`,
64
+ };
65
+ }
66
+ if (containsEquivalentPoint(currentFrontier, candidateEntry, objectives)) {
67
+ return {
68
+ outcome: "rejected",
69
+ frontierChanged: false,
70
+ reason: `candidate matches an existing pareto point on objectives ${describeObjectives(objectives)}`,
71
+ };
72
+ }
73
+ return {
74
+ outcome: "accepted",
75
+ frontierChanged: true,
76
+ reason: `candidate is non-dominated and expands the pareto frontier on objectives ${describeObjectives(objectives)}`,
77
+ };
78
+ }
79
+ export function updateParetoFrontier(currentFrontier, candidateEntry, objectives, tieBreaker = "hypervolume", referencePoint) {
80
+ const comparison = compareParetoFrontier(currentFrontier, candidateEntry, objectives);
81
+ if (!comparison.frontierChanged) {
82
+ return {
83
+ entries: currentFrontier,
84
+ comparison,
85
+ };
86
+ }
87
+ const retained = currentFrontier.filter((entry) => !dominates(candidateEntry, entry, objectives));
88
+ const nextEntries = [...retained, candidateEntry];
89
+ return {
90
+ entries: tieBreaker === "hypervolume"
91
+ ? sortByHypervolumeContribution(nextEntries, objectives, referencePoint)
92
+ : nextEntries,
93
+ comparison,
94
+ };
95
+ }
96
+ export function directionalDelta(candidate, incumbent) {
97
+ if (candidate.direction !== incumbent.direction) {
98
+ throw new Error(`metric direction mismatch for ${candidate.metricId}: candidate=${candidate.direction}, incumbent=${incumbent.direction}`);
99
+ }
100
+ return candidate.direction === "maximize"
101
+ ? candidate.value - incumbent.value
102
+ : incumbent.value - candidate.value;
103
+ }
104
+ export function buildMetricComparisonReason(metricId, candidate, incumbent, delta, verdict) {
105
+ const candidateText = `${metricId} candidate=${candidate.value}`;
106
+ const incumbentText = `${metricId} incumbent=${incumbent.value}`;
107
+ const deltaText = `delta=${delta.toFixed(4)}`;
108
+ return verdict === "improved"
109
+ ? `${candidateText}; ${incumbentText}; ${deltaText}; candidate improved frontier`
110
+ : `${candidateText}; ${incumbentText}; ${deltaText}; candidate did not improve frontier`;
111
+ }
112
+ function getMetric(entry, metricId) {
113
+ const metric = entry.metrics[metricId];
114
+ if (!metric) {
115
+ throw new Error(`Frontier entry ${entry.frontierId} is missing primary metric "${metricId}"`);
116
+ }
117
+ return metric;
118
+ }
119
+ function assertSingleBestShape(currentFrontier) {
120
+ if (currentFrontier.length > 1) {
121
+ throw new Error(`single_best frontier expected at most one entry, received ${currentFrontier.length}`);
122
+ }
123
+ }
124
+ function dominates(candidate, incumbent, objectives) {
125
+ let strictlyBetter = false;
126
+ for (const objective of objectives) {
127
+ const candidateMetric = getMetric(candidate, objective.metric);
128
+ const incumbentMetric = getMetric(incumbent, objective.metric);
129
+ const delta = directionalDelta(candidateMetric, incumbentMetric);
130
+ if (delta < -objective.epsilon) {
131
+ return false;
132
+ }
133
+ if (delta > objective.epsilon) {
134
+ strictlyBetter = true;
135
+ }
136
+ }
137
+ return strictlyBetter;
138
+ }
139
+ function containsEquivalentPoint(frontier, candidate, objectives) {
140
+ return frontier.some((entry) => objectives.every((objective) => {
141
+ const candidateMetric = getMetric(candidate, objective.metric);
142
+ const incumbentMetric = getMetric(entry, objective.metric);
143
+ const delta = Math.abs(directionalDelta(candidateMetric, incumbentMetric));
144
+ return delta <= objective.epsilon;
145
+ }));
146
+ }
147
+ function sortByHypervolumeContribution(entries, objectives, referencePoint) {
148
+ const resolvedReferencePoint = referencePoint ??
149
+ Object.fromEntries(objectives.map((objective) => {
150
+ const values = entries.map((entry) => getMetric(entry, objective.metric).value);
151
+ const direction = getMetric(entries[0], objective.metric).direction;
152
+ const worstValue = direction === "maximize" ? Math.min(...values) - 1 : Math.max(...values) + 1;
153
+ return [objective.metric, worstValue];
154
+ }));
155
+ return [...entries].sort((left, right) => {
156
+ const contributionDelta = hypervolumeContribution(right, objectives, resolvedReferencePoint) -
157
+ hypervolumeContribution(left, objectives, resolvedReferencePoint);
158
+ if (contributionDelta !== 0) {
159
+ return contributionDelta;
160
+ }
161
+ return left.frontierId.localeCompare(right.frontierId);
162
+ });
163
+ }
164
+ function hypervolumeContribution(entry, objectives, referencePoint) {
165
+ return objectives.reduce((product, objective) => {
166
+ const metric = getMetric(entry, objective.metric);
167
+ const reference = referencePoint[objective.metric];
168
+ if (reference === undefined) {
169
+ throw new Error(`Missing reference point for pareto objective "${objective.metric}"`);
170
+ }
171
+ const contribution = metric.direction === "maximize" ? metric.value - reference : reference - metric.value;
172
+ return product * Math.max(contribution, 0);
173
+ }, 1);
174
+ }
175
+ function describeObjectives(objectives) {
176
+ return objectives.map((objective) => objective.metric).join(", ");
177
+ }
178
+ //# sourceMappingURL=frontier-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontier-engine.js","sourceRoot":"","sources":["../../../src/core/state/frontier-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { ParetoObjectiveConfig, RatchetConfig } from "../manifest/schema.js";
2
+ import type { FrontierEntry } from "../model/frontier-entry.js";
3
+ import type { MetricResult } from "../model/metric.js";
4
+ export interface RatchetDecision {
5
+ outcome: "accepted" | "rejected" | "needs_human";
6
+ frontierChanged: boolean;
7
+ metricId: string;
8
+ policyType: RatchetConfig["type"];
9
+ reason: string;
10
+ delta?: number;
11
+ graduation?: {
12
+ activatedPolicy: "epsilon_improve";
13
+ consecutiveAccepts: number;
14
+ epsilon: number;
15
+ effectiveNextCycle: true;
16
+ reason: string;
17
+ };
18
+ }
19
+ export interface RatchetInput {
20
+ ratchet: RatchetConfig;
21
+ primaryMetric: string;
22
+ candidateMetrics: Record<string, MetricResult>;
23
+ currentFrontier: FrontierEntry[];
24
+ constraintFailureReason?: string;
25
+ priorConsecutiveAccepts?: number;
26
+ paretoObjectives?: ParetoObjectiveConfig[];
27
+ }
28
+ export declare function evaluateRatchet(input: RatchetInput): RatchetDecision;
@@ -0,0 +1,177 @@
1
+ import { buildMetricComparisonReason, compareParetoFrontier, directionalDelta } from "./frontier-engine.js";
2
+ export function evaluateRatchet(input) {
3
+ const metricId = "metric" in input.ratchet ? input.ratchet.metric ?? input.primaryMetric : input.primaryMetric;
4
+ if (input.constraintFailureReason) {
5
+ return {
6
+ outcome: "rejected",
7
+ frontierChanged: false,
8
+ metricId,
9
+ policyType: input.ratchet.type,
10
+ reason: input.constraintFailureReason,
11
+ };
12
+ }
13
+ const candidateMetric = getCandidateMetric(input.candidateMetrics, metricId);
14
+ const incumbentMetric = input.currentFrontier[0]?.metrics[metricId];
15
+ const priorConsecutiveAccepts = input.priorConsecutiveAccepts ?? 0;
16
+ if (input.ratchet.type === "pareto_dominance") {
17
+ if (!input.paretoObjectives) {
18
+ throw new Error("pareto_dominance ratchet requires paretoObjectives");
19
+ }
20
+ return evaluateParetoDominance(metricId, input.currentFrontier, input.candidateMetrics, input.paretoObjectives);
21
+ }
22
+ if (input.ratchet.type === "epsilon_improve") {
23
+ return evaluateEpsilonImprove(metricId, input.ratchet.epsilon, candidateMetric, incumbentMetric);
24
+ }
25
+ if (input.ratchet.graduation && priorConsecutiveAccepts >= input.ratchet.graduation.consecutiveAccepts) {
26
+ const graduatedDecision = evaluateEpsilonImprove(metricId, input.ratchet.graduation.epsilon, candidateMetric, incumbentMetric);
27
+ return {
28
+ ...graduatedDecision,
29
+ reason: `graduated autonomy active after ${priorConsecutiveAccepts} consecutive accepts; ${graduatedDecision.reason}`,
30
+ };
31
+ }
32
+ return evaluateApprovalGate(metricId, input.ratchet.minConfidence, candidateMetric, incumbentMetric, input.ratchet.graduation, priorConsecutiveAccepts);
33
+ }
34
+ function evaluateParetoDominance(metricId, currentFrontier, candidateMetrics, objectives) {
35
+ const candidateEntry = {
36
+ frontierId: "candidate-preview",
37
+ runId: "candidate-preview",
38
+ candidateId: "candidate-preview",
39
+ acceptedAt: new Date(0).toISOString(),
40
+ metrics: candidateMetrics,
41
+ artifacts: [],
42
+ };
43
+ const comparison = compareParetoFrontier(currentFrontier, candidateEntry, objectives);
44
+ return {
45
+ outcome: comparison.outcome,
46
+ frontierChanged: comparison.frontierChanged,
47
+ metricId,
48
+ policyType: "pareto_dominance",
49
+ reason: comparison.reason,
50
+ };
51
+ }
52
+ function evaluateEpsilonImprove(metricId, epsilon, candidateMetric, incumbentMetric) {
53
+ if (!incumbentMetric) {
54
+ return {
55
+ outcome: "accepted",
56
+ frontierChanged: true,
57
+ metricId,
58
+ policyType: "epsilon_improve",
59
+ reason: `frontier empty; candidate accepted on metric ${metricId}`,
60
+ };
61
+ }
62
+ const delta = directionalDelta(candidateMetric, incumbentMetric);
63
+ if (delta > epsilon) {
64
+ return {
65
+ outcome: "accepted",
66
+ frontierChanged: true,
67
+ metricId,
68
+ policyType: "epsilon_improve",
69
+ delta,
70
+ reason: `${buildMetricComparisonReason(metricId, candidateMetric, incumbentMetric, delta, "improved")}; epsilon=${epsilon}`,
71
+ };
72
+ }
73
+ return {
74
+ outcome: "rejected",
75
+ frontierChanged: false,
76
+ metricId,
77
+ policyType: "epsilon_improve",
78
+ delta,
79
+ reason: `${buildMetricComparisonReason(metricId, candidateMetric, incumbentMetric, delta, "not_improved")}; epsilon=${epsilon}`,
80
+ };
81
+ }
82
+ function evaluateApprovalGate(metricId, minConfidence, candidateMetric, incumbentMetric, graduation, priorConsecutiveAccepts = 0) {
83
+ const confidence = candidateMetric.confidence;
84
+ if (!incumbentMetric) {
85
+ if (confidence === undefined) {
86
+ return {
87
+ outcome: "needs_human",
88
+ frontierChanged: false,
89
+ metricId,
90
+ policyType: "approval_gate",
91
+ reason: `frontier empty on metric ${metricId}, but candidate confidence is missing`,
92
+ };
93
+ }
94
+ if (confidence < minConfidence) {
95
+ return {
96
+ outcome: "needs_human",
97
+ frontierChanged: false,
98
+ metricId,
99
+ policyType: "approval_gate",
100
+ reason: `frontier empty on metric ${metricId}, but confidence ${confidence.toFixed(2)} is below threshold ${minConfidence.toFixed(2)}`,
101
+ };
102
+ }
103
+ return {
104
+ outcome: "accepted",
105
+ frontierChanged: true,
106
+ metricId,
107
+ policyType: "approval_gate",
108
+ reason: `frontier empty; candidate accepted on metric ${metricId} with confidence ${confidence.toFixed(2)}`,
109
+ ...(graduation && priorConsecutiveAccepts + 1 >= graduation.consecutiveAccepts
110
+ ? {
111
+ graduation: buildGraduationEvent(metricId, graduation, priorConsecutiveAccepts + 1),
112
+ }
113
+ : {}),
114
+ };
115
+ }
116
+ const delta = directionalDelta(candidateMetric, incumbentMetric);
117
+ if (delta <= 0) {
118
+ return {
119
+ outcome: "rejected",
120
+ frontierChanged: false,
121
+ metricId,
122
+ policyType: "approval_gate",
123
+ delta,
124
+ reason: buildMetricComparisonReason(metricId, candidateMetric, incumbentMetric, delta, "not_improved"),
125
+ };
126
+ }
127
+ if (confidence === undefined) {
128
+ return {
129
+ outcome: "needs_human",
130
+ frontierChanged: false,
131
+ metricId,
132
+ policyType: "approval_gate",
133
+ delta,
134
+ reason: `${buildMetricComparisonReason(metricId, candidateMetric, incumbentMetric, delta, "improved")}; confidence missing`,
135
+ };
136
+ }
137
+ if (confidence < minConfidence) {
138
+ return {
139
+ outcome: "needs_human",
140
+ frontierChanged: false,
141
+ metricId,
142
+ policyType: "approval_gate",
143
+ delta,
144
+ reason: `${buildMetricComparisonReason(metricId, candidateMetric, incumbentMetric, delta, "improved")}; confidence ${confidence.toFixed(2)} below threshold ${minConfidence.toFixed(2)}`,
145
+ };
146
+ }
147
+ return {
148
+ outcome: "accepted",
149
+ frontierChanged: true,
150
+ metricId,
151
+ policyType: "approval_gate",
152
+ delta,
153
+ reason: `${buildMetricComparisonReason(metricId, candidateMetric, incumbentMetric, delta, "improved")}; confidence ${confidence.toFixed(2)} passed threshold ${minConfidence.toFixed(2)}`,
154
+ ...(graduation && priorConsecutiveAccepts + 1 >= graduation.consecutiveAccepts
155
+ ? {
156
+ graduation: buildGraduationEvent(metricId, graduation, priorConsecutiveAccepts + 1),
157
+ }
158
+ : {}),
159
+ };
160
+ }
161
+ function buildGraduationEvent(metricId, graduation, consecutiveAccepts) {
162
+ return {
163
+ activatedPolicy: "epsilon_improve",
164
+ consecutiveAccepts,
165
+ epsilon: graduation.epsilon,
166
+ effectiveNextCycle: true,
167
+ reason: `graduated autonomy unlocked for metric ${metricId} after ${consecutiveAccepts} consecutive accepts`,
168
+ };
169
+ }
170
+ function getCandidateMetric(metrics, metricId) {
171
+ const metric = metrics[metricId];
172
+ if (!metric) {
173
+ throw new Error(`Missing candidate metric "${metricId}" for ratchet evaluation`);
174
+ }
175
+ return metric;
176
+ }
177
+ //# sourceMappingURL=ratchet-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratchet-engine.js","sourceRoot":"","sources":["../../../src/core/state/ratchet-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type RunPhase, type RunRecord, type RunStatus } from "../model/run-record.js";
2
+ export type PendingAction = RunRecord["pendingAction"];
3
+ export interface AdvanceRunPhaseOptions {
4
+ at?: string;
5
+ status?: RunStatus;
6
+ decisionId?: string;
7
+ error?: RunRecord["error"];
8
+ pendingAction?: PendingAction;
9
+ }
10
+ export interface RecoveryPlan {
11
+ resumable: boolean;
12
+ nextAction: PendingAction;
13
+ reason: string;
14
+ }
15
+ export declare function advanceRunPhase(run: RunRecord, nextPhase: RunPhase, options?: AdvanceRunPhaseOptions): RunRecord;
16
+ export declare function canResume(run: RunRecord): boolean;
17
+ export declare function recoverRun(run: RunRecord): RecoveryPlan;
@@ -0,0 +1,94 @@
1
+ import { pendingActionSchema, runRecordSchema, } from "../model/run-record.js";
2
+ const phaseOrder = [
3
+ "proposed",
4
+ "executed",
5
+ "evaluated",
6
+ "decision_written",
7
+ "committed",
8
+ "frontier_updated",
9
+ "completed",
10
+ "failed",
11
+ ];
12
+ export function advanceRunPhase(run, nextPhase, options = {}) {
13
+ const currentIndex = phaseOrder.indexOf(run.phase);
14
+ const nextIndex = phaseOrder.indexOf(nextPhase);
15
+ if (nextIndex < currentIndex) {
16
+ return runRecordSchema.parse(run);
17
+ }
18
+ const resolvedPhase = nextIndex === currentIndex ? run.phase : nextPhase;
19
+ const status = options.status ?? inferStatusForPhase(run.status, resolvedPhase);
20
+ const pendingAction = options.pendingAction ?? inferPendingAction({ ...run, status, phase: resolvedPhase });
21
+ const updated = runRecordSchema.parse({
22
+ ...run,
23
+ status,
24
+ phase: resolvedPhase,
25
+ pendingAction: pendingActionSchema.parse(pendingAction),
26
+ decisionId: options.decisionId ?? run.decisionId,
27
+ error: options.error ?? run.error,
28
+ endedAt: resolvedPhase === "completed" || resolvedPhase === "failed" ? options.at ?? run.endedAt ?? new Date().toISOString() : run.endedAt,
29
+ });
30
+ return updated;
31
+ }
32
+ export function canResume(run) {
33
+ return recoverRun(run).resumable;
34
+ }
35
+ export function recoverRun(run) {
36
+ if (run.phase === "completed") {
37
+ return {
38
+ resumable: false,
39
+ nextAction: "none",
40
+ reason: "run already completed",
41
+ };
42
+ }
43
+ if (run.phase === "failed") {
44
+ return {
45
+ resumable: false,
46
+ nextAction: "none",
47
+ reason: "run failed and requires explicit intervention",
48
+ };
49
+ }
50
+ const nextAction = run.pendingAction !== "none" ? run.pendingAction : inferPendingAction(run);
51
+ return {
52
+ resumable: nextAction !== "none",
53
+ nextAction,
54
+ reason: `resume from phase ${run.phase} with action ${nextAction}`,
55
+ };
56
+ }
57
+ function inferStatusForPhase(currentStatus, phase) {
58
+ switch (phase) {
59
+ case "proposed":
60
+ case "executed":
61
+ return "running";
62
+ case "evaluated":
63
+ return currentStatus === "rejected" || currentStatus === "needs_human" || currentStatus === "accepted"
64
+ ? currentStatus
65
+ : "evaluated";
66
+ case "decision_written":
67
+ case "committed":
68
+ case "frontier_updated":
69
+ case "completed":
70
+ return currentStatus;
71
+ case "failed":
72
+ return "failed";
73
+ }
74
+ }
75
+ function inferPendingAction(run) {
76
+ switch (run.phase) {
77
+ case "proposed":
78
+ return "execute_experiment";
79
+ case "executed":
80
+ return "evaluate_metrics";
81
+ case "evaluated":
82
+ return "write_decision";
83
+ case "decision_written":
84
+ return run.status === "accepted" ? "commit_candidate" : "cleanup_workspace";
85
+ case "committed":
86
+ return "update_frontier";
87
+ case "frontier_updated":
88
+ return "cleanup_workspace";
89
+ case "completed":
90
+ case "failed":
91
+ return "none";
92
+ }
93
+ }
94
+ //# sourceMappingURL=run-state-machine.js.map