ralph-research 0.1.4 → 0.1.5
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 +20 -5
- package/dist/cli/commands/demo.js +4 -3
- package/dist/cli/commands/demo.js.map +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
- package/templates/code/ralph.yaml +57 -0
- package/templates/code/scripts/experiment.mjs +29 -0
- package/templates/code/scripts/metric.mjs +8 -0
- package/templates/code/scripts/propose.mjs +14 -0
- package/templates/code/src/calculator.mjs +7 -0
- package/templates/code/tests/calculator.test.mjs +20 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# ralph-research
|
|
2
2
|
|
|
3
3
|
[](https://github.com/coyaSONG/ralph-research/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/ralph-research)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
[](package.json)
|
|
6
7
|
[](tsconfig.json)
|
|
@@ -44,8 +45,8 @@ The current product bar is reliability, not breadth. The bundled success path is
|
|
|
44
45
|
| If you want to... | Use |
|
|
45
46
|
| --- | --- |
|
|
46
47
|
| Check whether a repo is runnable | `rrx validate` then `rrx doctor` |
|
|
47
|
-
| Materialize the bundled example project | `rrx init --template writing` |
|
|
48
|
-
| Run a disposable end-to-end demo | `rrx demo writing` |
|
|
48
|
+
| Materialize the bundled example project | `rrx init --template writing` (or `--template code`) |
|
|
49
|
+
| Run a disposable end-to-end demo | `rrx demo writing` (or `rrx demo code`) |
|
|
49
50
|
| Launch the v1 goal-driven orchestrator | `rrx "improve the holdout top-3 model"` |
|
|
50
51
|
| Launch the v1 goal-driven orchestrator explicitly | `rrx launch "improve the holdout top-3 model"` |
|
|
51
52
|
| Resume a persisted TUI research session | `rrx resume latest` |
|
|
@@ -103,7 +104,7 @@ See [docs/operation-model.md](docs/operation-model.md) for the full lifecycle an
|
|
|
103
104
|
|
|
104
105
|
## Current Scope
|
|
105
106
|
|
|
106
|
-
- Bundled
|
|
107
|
+
- Bundled templates: `writing` (prose ratchet) and `code` (test-pass ratchet over a tiny calculator module)
|
|
107
108
|
- Default template metric: local command metric, no API key required
|
|
108
109
|
- Optional judge path: pairwise LLM judge packs
|
|
109
110
|
- MCP tools:
|
|
@@ -113,9 +114,11 @@ See [docs/operation-model.md](docs/operation-model.md) for the full lifecycle an
|
|
|
113
114
|
|
|
114
115
|
The runtime supports broader manifests than the bundled template demonstrates, but the shipped onboarding path is intentionally narrow until those flows are equally reliable.
|
|
115
116
|
|
|
116
|
-
##
|
|
117
|
+
## Bundled Templates
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
### Writing template
|
|
120
|
+
|
|
121
|
+
Self-contained prose improvement loop:
|
|
119
122
|
|
|
120
123
|
- `docs/draft.md`: sample draft
|
|
121
124
|
- `scripts/propose.mjs`: bounded rewrite
|
|
@@ -125,6 +128,18 @@ The bundled writing template is self-contained:
|
|
|
125
128
|
|
|
126
129
|
`templates/writing/ralph.yaml` uses a local command metric by default, so the first run works without model credentials.
|
|
127
130
|
|
|
131
|
+
### Code template
|
|
132
|
+
|
|
133
|
+
Self-contained test-pass ratchet over a tiny calculator module:
|
|
134
|
+
|
|
135
|
+
- `src/calculator.mjs`: deliberately-broken `sum`/`multiply`
|
|
136
|
+
- `tests/calculator.test.mjs`: four assertions using the built-in `node:test` runner
|
|
137
|
+
- `scripts/propose.mjs`: writes the fixed calculator implementation
|
|
138
|
+
- `scripts/experiment.mjs`: runs `node --test --test-reporter=tap` and persists the pass/fail counts
|
|
139
|
+
- `scripts/metric.mjs`: emits the pass count as the `tests_passed` metric
|
|
140
|
+
|
|
141
|
+
`rrx demo code` materializes the template, runs one cycle, and shows the ratchet promoting the candidate from `tests_passed: 0` to `tests_passed: 4`.
|
|
142
|
+
|
|
128
143
|
## Progressive Runs
|
|
129
144
|
|
|
130
145
|
`rrx run` executes one cycle by default and auto-resumes the latest recoverable run when one exists.
|
|
@@ -15,8 +15,9 @@ const defaultCommandIO = {
|
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
17
|
export async function runDemoCommand(template, options, io = defaultCommandIO) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const supportedTemplates = ["writing", "code"];
|
|
19
|
+
if (!supportedTemplates.includes(template)) {
|
|
20
|
+
const message = `Unsupported demo template ${template}; supported templates: ${supportedTemplates.join(", ")}`;
|
|
20
21
|
if (options.json) {
|
|
21
22
|
io.stderr(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
22
23
|
}
|
|
@@ -28,7 +29,7 @@ export async function runDemoCommand(template, options, io = defaultCommandIO) {
|
|
|
28
29
|
try {
|
|
29
30
|
const targetDir = options.path
|
|
30
31
|
? resolve(options.path)
|
|
31
|
-
: await mkdtemp(join(tmpdir(),
|
|
32
|
+
: await mkdtemp(join(tmpdir(), `rrx-demo-${template}-`));
|
|
32
33
|
if (options.force) {
|
|
33
34
|
await rm(targetDir, { recursive: true, force: true });
|
|
34
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../../../src/cli/commands/demo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAS9D,MAAM,gBAAgB,GAAc;IAClC,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvC,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,OAA2B,EAC3B,KAAgB,gBAAgB;IAEhC,IAAI,QAAQ,
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../../../src/cli/commands/demo.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/program.js
CHANGED
|
@@ -18,7 +18,7 @@ export function createProgram(dependencies = {}) {
|
|
|
18
18
|
program
|
|
19
19
|
.name("rrx")
|
|
20
20
|
.description("Local-first runtime for recursive research improvement.")
|
|
21
|
-
.version("0.1.
|
|
21
|
+
.version("0.1.5")
|
|
22
22
|
.argument("[goal]", "Goal to pursue through the v1 TUI research orchestrator")
|
|
23
23
|
.action(async (goal) => {
|
|
24
24
|
if (goal === undefined) {
|
package/dist/mcp/server.js
CHANGED
|
@@ -14,7 +14,7 @@ export function createRalphResearchMcpServer(options = {}) {
|
|
|
14
14
|
(() => new ResearchSessionRecoveryService());
|
|
15
15
|
const server = new McpServer({
|
|
16
16
|
name: "ralph-research",
|
|
17
|
-
version: "0.1.
|
|
17
|
+
version: "0.1.5",
|
|
18
18
|
});
|
|
19
19
|
server.registerTool("run_research_cycle", {
|
|
20
20
|
description: "Run one or more research cycles using the shared ralph-research service layer.",
|
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
schemaVersion: "0.1"
|
|
2
|
+
|
|
3
|
+
project:
|
|
4
|
+
name: code-demo
|
|
5
|
+
artifact: code
|
|
6
|
+
baselineRef: main
|
|
7
|
+
workspace: git
|
|
8
|
+
|
|
9
|
+
scope:
|
|
10
|
+
allowedGlobs:
|
|
11
|
+
- "src/**"
|
|
12
|
+
- "tests/**"
|
|
13
|
+
- "out/**"
|
|
14
|
+
maxFilesChanged: 2
|
|
15
|
+
maxLineDelta: 40
|
|
16
|
+
|
|
17
|
+
proposer:
|
|
18
|
+
type: command
|
|
19
|
+
command: "node scripts/propose.mjs"
|
|
20
|
+
|
|
21
|
+
experiment:
|
|
22
|
+
run:
|
|
23
|
+
command: "node scripts/experiment.mjs"
|
|
24
|
+
outputs:
|
|
25
|
+
- id: test-results
|
|
26
|
+
path: out/test-results.json
|
|
27
|
+
|
|
28
|
+
metrics:
|
|
29
|
+
catalog:
|
|
30
|
+
- id: tests_passed
|
|
31
|
+
kind: numeric
|
|
32
|
+
direction: maximize
|
|
33
|
+
extractor:
|
|
34
|
+
type: command
|
|
35
|
+
command: "node scripts/metric.mjs"
|
|
36
|
+
parser: plain_number
|
|
37
|
+
|
|
38
|
+
constraints: []
|
|
39
|
+
|
|
40
|
+
frontier:
|
|
41
|
+
strategy: single_best
|
|
42
|
+
primaryMetric: tests_passed
|
|
43
|
+
|
|
44
|
+
ratchet:
|
|
45
|
+
type: epsilon_improve
|
|
46
|
+
metric: tests_passed
|
|
47
|
+
epsilon: 0
|
|
48
|
+
|
|
49
|
+
# Optional progressive-stop contract:
|
|
50
|
+
# stopping:
|
|
51
|
+
# target:
|
|
52
|
+
# metric: tests_passed
|
|
53
|
+
# op: ">="
|
|
54
|
+
# value: 4
|
|
55
|
+
|
|
56
|
+
storage:
|
|
57
|
+
root: .ralph
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
mkdirSync(join(process.cwd(), "out"), { recursive: true });
|
|
6
|
+
|
|
7
|
+
const result = spawnSync(
|
|
8
|
+
process.execPath,
|
|
9
|
+
["--test", "--test-reporter=tap", "tests/calculator.test.mjs"],
|
|
10
|
+
{
|
|
11
|
+
cwd: process.cwd(),
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
},
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const combined = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
17
|
+
const passMatch = combined.match(/# pass (\d+)/);
|
|
18
|
+
const failMatch = combined.match(/# fail (\d+)/);
|
|
19
|
+
|
|
20
|
+
const passed = passMatch ? Number(passMatch[1]) : 0;
|
|
21
|
+
const failed = failMatch ? Number(failMatch[1]) : 0;
|
|
22
|
+
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(process.cwd(), "out", "test-results.json"),
|
|
25
|
+
`${JSON.stringify({ passed, failed }, null, 2)}\n`,
|
|
26
|
+
"utf8",
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
console.log("experiment complete");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const fixedCalculator = `export function sum(a, b) {
|
|
5
|
+
return a + b;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function multiply(a, b) {
|
|
9
|
+
return a * b;
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
writeFileSync(join(process.cwd(), "src", "calculator.mjs"), fixedCalculator, "utf8");
|
|
14
|
+
console.log("proposal complete");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
3
|
+
|
|
4
|
+
import { multiply, sum } from "../src/calculator.mjs";
|
|
5
|
+
|
|
6
|
+
test("sum adds two positive integers", () => {
|
|
7
|
+
assert.equal(sum(2, 3), 5);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("sum handles a zero operand", () => {
|
|
11
|
+
assert.equal(sum(0, 7), 7);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("multiply multiplies two positive integers", () => {
|
|
15
|
+
assert.equal(multiply(3, 4), 12);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("multiply by one is identity", () => {
|
|
19
|
+
assert.equal(multiply(1, 9), 9);
|
|
20
|
+
});
|