@possumtech/rummy 2.1.0 → 2.2.1
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/.env.example +40 -15
- package/.xai.key +1 -0
- package/PLUGINS.md +169 -53
- package/README.md +38 -32
- package/SPEC.md +366 -179
- package/bin/digest.js +1097 -0
- package/biome/no-fallbacks.grit +2 -2
- package/gemini.key +1 -0
- package/lang/en.json +10 -1
- package/migrations/001_initial_schema.sql +9 -2
- package/package.json +19 -8
- package/service.js +1 -0
- package/src/agent/AgentLoop.js +76 -26
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/Entries.js +238 -60
- package/src/agent/ProjectAgent.js +44 -0
- package/src/agent/TurnExecutor.js +99 -30
- package/src/agent/XmlParser.js +206 -111
- package/src/agent/errors.js +35 -0
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +3 -42
- package/src/agent/materializeContext.js +30 -1
- package/src/agent/runs.sql +8 -18
- package/src/agent/tokens.js +0 -1
- package/src/agent/turns.sql +1 -0
- package/src/hooks/Hooks.js +26 -0
- package/src/hooks/RummyContext.js +12 -1
- package/src/lib/hedberg/README.md +60 -0
- package/src/lib/hedberg/hedberg.js +60 -0
- package/src/lib/hedberg/marker.js +158 -0
- package/src/{plugins → lib}/hedberg/matcher.js +1 -2
- package/src/llm/LlmProvider.js +41 -3
- package/src/llm/openaiStream.js +17 -0
- package/src/plugins/ask_user/ask_user.js +12 -2
- package/src/plugins/ask_user/ask_userDoc.md +1 -5
- package/src/plugins/budget/README.md +29 -24
- package/src/plugins/budget/budget.js +166 -110
- package/src/plugins/cli/README.md +3 -4
- package/src/plugins/cli/cli.js +31 -5
- package/src/plugins/cloudflare/cloudflare.js +136 -0
- package/src/plugins/cp/cp.js +41 -4
- package/src/plugins/cp/cpDoc.md +5 -6
- package/src/plugins/engine/engine.sql +1 -1
- package/src/plugins/env/README.md +5 -4
- package/src/plugins/env/env.js +7 -4
- package/src/plugins/env/envDoc.md +7 -8
- package/src/plugins/error/error.js +56 -15
- package/src/plugins/file/README.md +12 -3
- package/src/plugins/file/file.js +2 -2
- package/src/plugins/get/get.js +59 -36
- package/src/plugins/get/getDoc.md +10 -34
- package/src/plugins/google/google.js +115 -0
- package/src/plugins/hedberg/hedberg.js +13 -56
- package/src/plugins/helpers.js +66 -12
- package/src/plugins/index.js +1 -2
- package/src/plugins/instructions/README.md +44 -47
- package/src/plugins/instructions/instructions-system.md +44 -0
- package/src/plugins/instructions/instructions-user.md +53 -0
- package/src/plugins/instructions/instructions.js +58 -189
- package/src/plugins/known/README.md +6 -7
- package/src/plugins/known/known.js +24 -30
- package/src/plugins/log/log.js +41 -32
- package/src/plugins/mv/mv.js +40 -1
- package/src/plugins/mv/mvDoc.md +1 -8
- package/src/plugins/ollama/ollama.js +4 -3
- package/src/plugins/openai/openai.js +4 -3
- package/src/plugins/openrouter/openrouter.js +14 -4
- package/src/plugins/persona/README.md +11 -13
- package/src/plugins/persona/default.md +29 -0
- package/src/plugins/persona/persona.js +10 -66
- package/src/plugins/policy/policy.js +23 -22
- package/src/plugins/prompt/README.md +37 -27
- package/src/plugins/prompt/prompt.js +13 -19
- package/src/plugins/rm/rm.js +18 -0
- package/src/plugins/rm/rmDoc.md +5 -6
- package/src/plugins/rpc/rpc.js +3 -3
- package/src/plugins/set/set.js +205 -323
- package/src/plugins/set/setDoc.md +47 -17
- package/src/plugins/sh/README.md +6 -5
- package/src/plugins/sh/sh.js +8 -5
- package/src/plugins/sh/shDoc.md +7 -8
- package/src/plugins/skill/README.md +37 -14
- package/src/plugins/skill/skill.js +200 -101
- package/src/plugins/skill/skillDoc.js +3 -0
- package/src/plugins/skill/skillDoc.md +9 -0
- package/src/plugins/stream/README.md +7 -6
- package/src/plugins/stream/finalize.js +100 -0
- package/src/plugins/stream/stream.js +13 -45
- package/src/plugins/telemetry/telemetry.js +27 -4
- package/src/plugins/think/think.js +2 -3
- package/src/plugins/think/thinkDoc.md +2 -4
- package/src/plugins/unknown/README.md +1 -1
- package/src/plugins/unknown/unknown.js +17 -19
- package/src/plugins/update/update.js +4 -51
- package/src/plugins/update/updateDoc.md +21 -6
- package/src/plugins/xai/xai.js +68 -102
- package/src/plugins/yolo/yolo.js +102 -75
- package/src/sql/functions/hedmatch.js +1 -1
- package/src/sql/functions/hedreplace.js +1 -1
- package/src/sql/functions/hedsearch.js +1 -1
- package/src/sql/functions/slugify.js +16 -2
- package/BENCH_ENVIRONMENT.md +0 -230
- package/CLIENT_INTERFACE.md +0 -396
- package/last_run.txt +0 -5617
- package/scriptify/ask_run.js +0 -77
- package/scriptify/cache_probe.js +0 -66
- package/scriptify/cache_probe_grok.js +0 -74
- package/src/agent/budget.js +0 -33
- package/src/agent/config.js +0 -38
- package/src/plugins/hedberg/README.md +0 -71
- package/src/plugins/hedberg/docs.md +0 -0
- package/src/plugins/hedberg/edits.js +0 -55
- package/src/plugins/hedberg/normalize.js +0 -17
- package/src/plugins/hedberg/sed.js +0 -49
- package/src/plugins/instructions/instructions.md +0 -34
- package/src/plugins/instructions/instructions_104.md +0 -8
- package/src/plugins/instructions/instructions_105.md +0 -39
- package/src/plugins/instructions/instructions_106.md +0 -22
- package/src/plugins/instructions/instructions_107.md +0 -17
- package/src/plugins/instructions/instructions_108.md +0 -0
- package/src/plugins/known/knownDoc.js +0 -3
- package/src/plugins/known/knownDoc.md +0 -8
- package/src/plugins/unknown/unknownDoc.js +0 -3
- package/src/plugins/unknown/unknownDoc.md +0 -11
- package/turns/cli_1777462658211/turn_001.txt +0 -772
- package/turns/cli_1777462658211/turn_002.txt +0 -606
- package/turns/cli_1777462658211/turn_003.txt +0 -667
- package/turns/cli_1777462658211/turn_004.txt +0 -297
- package/turns/cli_1777462658211/turn_005.txt +0 -301
- package/turns/cli_1777462658211/turn_006.txt +0 -262
- package/turns/cli_1777465095132/turn_001.txt +0 -715
- package/turns/cli_1777465095132/turn_002.txt +0 -236
- package/turns/cli_1777465095132/turn_003.txt +0 -287
- package/turns/cli_1777465095132/turn_004.txt +0 -694
- package/turns/cli_1777465095132/turn_005.txt +0 -422
- package/turns/cli_1777465095132/turn_006.txt +0 -365
- package/turns/cli_1777465095132/turn_007.txt +0 -885
- package/turns/cli_1777465095132/turn_008.txt +0 -1277
- package/turns/cli_1777465095132/turn_009.txt +0 -736
- /package/src/{plugins → lib}/hedberg/patterns.js +0 -0
package/scriptify/ask_run.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inject a follow-up question into an existing LME run and print the answer.
|
|
3
|
-
*
|
|
4
|
-
* Usage: node scriptify/ask_run.js <db_path> <run_alias> "your question"
|
|
5
|
-
*
|
|
6
|
-
* Reuses the run's full ingested context so the model answers with all
|
|
7
|
-
* its accumulated knowledge. Used as a debugging tool to interrogate
|
|
8
|
-
* the model's reasoning after a benchmark completes.
|
|
9
|
-
*/
|
|
10
|
-
import TestDb from "../test/helpers/TestDb.js";
|
|
11
|
-
import TestServer from "../test/helpers/TestServer.js";
|
|
12
|
-
import RpcClient from "../test/helpers/RpcClient.js";
|
|
13
|
-
|
|
14
|
-
const [, , dbPath, alias, ...questionParts] = process.argv;
|
|
15
|
-
const question = questionParts.join(" ");
|
|
16
|
-
|
|
17
|
-
if (!dbPath || !alias || !question) {
|
|
18
|
-
console.error(
|
|
19
|
-
'Usage: node scriptify/ask_run.js <db_path> <run_alias> "your question"',
|
|
20
|
-
);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const tdb = await TestDb.createAt(dbPath);
|
|
25
|
-
const tserver = await TestServer.start(tdb);
|
|
26
|
-
const client = new RpcClient(tserver.url);
|
|
27
|
-
await client.connect();
|
|
28
|
-
await client.call("rummy/hello", {
|
|
29
|
-
name: "ask_run",
|
|
30
|
-
projectRoot: "/tmp/rummy-lme",
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
console.log(`Asking ${alias}: ${question}\n`);
|
|
34
|
-
|
|
35
|
-
const TERMINAL = [200, 204, 413, 422, 499, 500];
|
|
36
|
-
const startRes = await client.call("set", {
|
|
37
|
-
path: `run://${alias}`,
|
|
38
|
-
body: question,
|
|
39
|
-
attributes: {
|
|
40
|
-
model: "grok",
|
|
41
|
-
mode: "ask",
|
|
42
|
-
noRepo: true,
|
|
43
|
-
noInteraction: true,
|
|
44
|
-
noWeb: true,
|
|
45
|
-
noProposals: true,
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const deadline = Date.now() + 600_000;
|
|
50
|
-
while (Date.now() < deadline) {
|
|
51
|
-
const row = await tdb.db.get_run_by_alias.get({ alias });
|
|
52
|
-
if (TERMINAL.includes(row.status)) break;
|
|
53
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const runRow = await tdb.db.get_run_by_alias.get({ alias });
|
|
57
|
-
const entries = await tdb.db.get_known_entries.all({ run_id: runRow.id });
|
|
58
|
-
const reasoning = entries
|
|
59
|
-
.filter((e) => e.scheme === "reasoning")
|
|
60
|
-
.toSorted((a, b) => b.turn - a.turn)[0];
|
|
61
|
-
const assistant = entries
|
|
62
|
-
.filter((e) => e.scheme === "assistant")
|
|
63
|
-
.toSorted((a, b) => b.turn - a.turn)[0];
|
|
64
|
-
|
|
65
|
-
if (reasoning) {
|
|
66
|
-
console.log("=== REASONING ===");
|
|
67
|
-
console.log(reasoning.body);
|
|
68
|
-
console.log("");
|
|
69
|
-
}
|
|
70
|
-
if (assistant) {
|
|
71
|
-
console.log("=== ANSWER ===");
|
|
72
|
-
console.log(assistant.body);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
await client.close();
|
|
76
|
-
await tserver.stop();
|
|
77
|
-
await tdb.cleanup();
|
package/scriptify/cache_probe.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Probe llama-server cache behavior. Send variations of the same request
|
|
3
|
-
// and inspect cached_tokens in the response usage block to determine
|
|
4
|
-
// whether caching is token-prefix or message-hash level.
|
|
5
|
-
|
|
6
|
-
const URL = "http://127.0.0.1:11435/v1/chat/completions";
|
|
7
|
-
const MODEL = "gemma-4-26B-A4B-it-UD-Q3_K_XL.gguf";
|
|
8
|
-
|
|
9
|
-
async function probe(label, system, user) {
|
|
10
|
-
const body = {
|
|
11
|
-
model: MODEL,
|
|
12
|
-
messages: [
|
|
13
|
-
{ role: "system", content: system },
|
|
14
|
-
{ role: "user", content: user },
|
|
15
|
-
],
|
|
16
|
-
think: true,
|
|
17
|
-
temperature: 0.5,
|
|
18
|
-
};
|
|
19
|
-
const res = await fetch(URL, {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: { "Content-Type": "application/json" },
|
|
22
|
-
body: JSON.stringify(body),
|
|
23
|
-
});
|
|
24
|
-
const data = await res.json();
|
|
25
|
-
const u = data.usage || {};
|
|
26
|
-
const cached =
|
|
27
|
-
u.prompt_tokens_details?.cached_tokens ??
|
|
28
|
-
u.cached_tokens ??
|
|
29
|
-
0;
|
|
30
|
-
console.log(
|
|
31
|
-
`[${label}] prompt_tokens=${u.prompt_tokens ?? "?"} cached_tokens=${cached} system_chars=${system.length} user_chars=${user.length}`,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const STATIC_SYSTEM_BASE = `You are a helpful assistant.
|
|
36
|
-
|
|
37
|
-
Tools available:
|
|
38
|
-
- foo: does foo
|
|
39
|
-
- bar: does bar
|
|
40
|
-
- baz: does baz
|
|
41
|
-
|
|
42
|
-
Always be concise.`;
|
|
43
|
-
|
|
44
|
-
const ADDITION_A = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
45
|
-
const ADDITION_B = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n<known path=\"k2\">second known fact</known>\n</context>";
|
|
46
|
-
const ADDITION_C = "\n\n<context>\n<known path=\"k2\">second known fact</known>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
47
|
-
|
|
48
|
-
const USER_A = "Hello.";
|
|
49
|
-
|
|
50
|
-
console.log("=== Run 1: baseline (cold, then immediate repeat) ===");
|
|
51
|
-
await probe("1a baseline cold", STATIC_SYSTEM_BASE, USER_A);
|
|
52
|
-
await probe("1b same-as-1a ", STATIC_SYSTEM_BASE, USER_A);
|
|
53
|
-
|
|
54
|
-
console.log("\n=== Run 2: same base, then base + appended context (prefix unchanged) ===");
|
|
55
|
-
await probe("2a base only ", STATIC_SYSTEM_BASE, USER_A);
|
|
56
|
-
await probe("2b base + 1 entry", STATIC_SYSTEM_BASE + ADDITION_A, USER_A);
|
|
57
|
-
await probe("2c base + 2 entries", STATIC_SYSTEM_BASE + ADDITION_B, USER_A);
|
|
58
|
-
|
|
59
|
-
console.log("\n=== Run 3: prefix change (entries reordered, same body) ===");
|
|
60
|
-
await probe("3a base + 2 entries (k1,k2)", STATIC_SYSTEM_BASE + ADDITION_B, USER_A);
|
|
61
|
-
await probe("3b base + 2 entries (k2,k1) reordered", STATIC_SYSTEM_BASE + ADDITION_C, USER_A);
|
|
62
|
-
|
|
63
|
-
console.log("\n=== Run 4: small mid-prefix change ===");
|
|
64
|
-
const MIDDIFF = STATIC_SYSTEM_BASE.replace("baz", "qux");
|
|
65
|
-
await probe("4a stable base ", STATIC_SYSTEM_BASE, USER_A);
|
|
66
|
-
await probe("4b changed baz→qux", MIDDIFF, USER_A);
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Same probe as cache_probe.js but against OpenRouter's grok endpoint.
|
|
3
|
-
// If cached_tokens behaves sanely (incremental matches preserve prefix),
|
|
4
|
-
// then llama-server's behavior was the local anomaly.
|
|
5
|
-
|
|
6
|
-
const URL = `${process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1"}/chat/completions`;
|
|
7
|
-
const MODEL = "x-ai/grok-4.1-fast";
|
|
8
|
-
|
|
9
|
-
if (!process.env.OPENROUTER_API_KEY) {
|
|
10
|
-
console.error("OPENROUTER_API_KEY required");
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function probe(label, system, user) {
|
|
15
|
-
const body = {
|
|
16
|
-
model: MODEL,
|
|
17
|
-
messages: [
|
|
18
|
-
{ role: "system", content: system },
|
|
19
|
-
{ role: "user", content: user },
|
|
20
|
-
],
|
|
21
|
-
include_reasoning: true,
|
|
22
|
-
temperature: 0.5,
|
|
23
|
-
};
|
|
24
|
-
const res = await fetch(URL, {
|
|
25
|
-
method: "POST",
|
|
26
|
-
headers: {
|
|
27
|
-
"Content-Type": "application/json",
|
|
28
|
-
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
|
29
|
-
},
|
|
30
|
-
body: JSON.stringify(body),
|
|
31
|
-
});
|
|
32
|
-
const data = await res.json();
|
|
33
|
-
const u = data.usage || {};
|
|
34
|
-
const cached =
|
|
35
|
-
u.prompt_tokens_details?.cached_tokens ??
|
|
36
|
-
u.cached_tokens ??
|
|
37
|
-
u.cache_read_input_tokens ??
|
|
38
|
-
0;
|
|
39
|
-
console.log(
|
|
40
|
-
`[${label}] prompt_tokens=${u.prompt_tokens ?? "?"} cached_tokens=${cached} system_chars=${system.length}`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const STATIC_SYSTEM_BASE = `You are a helpful assistant.
|
|
45
|
-
|
|
46
|
-
Tools available:
|
|
47
|
-
- foo: does foo
|
|
48
|
-
- bar: does bar
|
|
49
|
-
- baz: does baz
|
|
50
|
-
|
|
51
|
-
Always be concise.`;
|
|
52
|
-
|
|
53
|
-
const ADDITION_A = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
54
|
-
const ADDITION_B = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n<known path=\"k2\">second known fact</known>\n</context>";
|
|
55
|
-
const ADDITION_C = "\n\n<context>\n<known path=\"k2\">second known fact</known>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
56
|
-
|
|
57
|
-
const USER = "Hello.";
|
|
58
|
-
|
|
59
|
-
console.log("=== Run 1: baseline (cold, then immediate repeat) ===");
|
|
60
|
-
await probe("1a baseline cold", STATIC_SYSTEM_BASE, USER);
|
|
61
|
-
await probe("1b same-as-1a ", STATIC_SYSTEM_BASE, USER);
|
|
62
|
-
|
|
63
|
-
console.log("\n=== Run 2: appended context (prefix unchanged) ===");
|
|
64
|
-
await probe("2a base + 1 ", STATIC_SYSTEM_BASE + ADDITION_A, USER);
|
|
65
|
-
await probe("2b base + 2 ", STATIC_SYSTEM_BASE + ADDITION_B, USER);
|
|
66
|
-
|
|
67
|
-
console.log("\n=== Run 3: reordered (entries shuffled) ===");
|
|
68
|
-
await probe("3a (k1,k2) ", STATIC_SYSTEM_BASE + ADDITION_B, USER);
|
|
69
|
-
await probe("3b (k2,k1) ", STATIC_SYSTEM_BASE + ADDITION_C, USER);
|
|
70
|
-
|
|
71
|
-
console.log("\n=== Run 4: mid-prefix character change ===");
|
|
72
|
-
const MIDDIFF = STATIC_SYSTEM_BASE.replace("baz", "qux");
|
|
73
|
-
await probe("4a stable base ", STATIC_SYSTEM_BASE, USER);
|
|
74
|
-
await probe("4b baz→qux ", MIDDIFF, USER);
|
package/src/agent/budget.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import config from "./config.js";
|
|
2
|
-
import { countTokens } from "./tokens.js";
|
|
3
|
-
|
|
4
|
-
const CEILING_RATIO = config.BUDGET_CEILING;
|
|
5
|
-
|
|
6
|
-
export function ceiling(contextSize) {
|
|
7
|
-
return Math.floor(contextSize * CEILING_RATIO);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Sum assembled-message token counts; used by the budget enforce gate.
|
|
11
|
-
export function measureMessages(messages) {
|
|
12
|
-
return messages.reduce((sum, m) => sum + countTokens(m.content), 0);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Sum projected row body token counts; used by prompt.js pre-assembly.
|
|
16
|
-
export function measureRows(rows) {
|
|
17
|
-
return rows.reduce((sum, r) => sum + countTokens(r.body), 0);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Single source of truth for budget numbers; tokenUsage echoes totalTokens for the wire attribute.
|
|
21
|
-
export function computeBudget({ contextSize, totalTokens }) {
|
|
22
|
-
const cap = ceiling(contextSize);
|
|
23
|
-
const tokensFree = Math.max(0, cap - totalTokens);
|
|
24
|
-
const overflow = Math.max(0, totalTokens - cap);
|
|
25
|
-
return {
|
|
26
|
-
ceiling: cap,
|
|
27
|
-
totalTokens,
|
|
28
|
-
tokenUsage: totalTokens,
|
|
29
|
-
tokensFree,
|
|
30
|
-
overflow,
|
|
31
|
-
ok: overflow === 0,
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/agent/config.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Validates required RUMMY_* env at module load; defaults in .env.example.
|
|
2
|
-
|
|
3
|
-
const REQUIRED = {
|
|
4
|
-
BUDGET_CEILING: { env: "RUMMY_BUDGET_CEILING", parse: Number },
|
|
5
|
-
LLM_DEADLINE: { env: "RUMMY_LLM_DEADLINE", parse: Number },
|
|
6
|
-
LLM_MAX_BACKOFF: { env: "RUMMY_LLM_MAX_BACKOFF", parse: Number },
|
|
7
|
-
FETCH_TIMEOUT: { env: "RUMMY_FETCH_TIMEOUT", parse: Number },
|
|
8
|
-
MAX_STRIKES: { env: "RUMMY_MAX_STRIKES", parse: Number },
|
|
9
|
-
MIN_CYCLES: { env: "RUMMY_MIN_CYCLES", parse: Number },
|
|
10
|
-
MAX_CYCLE_PERIOD: { env: "RUMMY_MAX_CYCLE_PERIOD", parse: Number },
|
|
11
|
-
RUN_TIMEOUT: { env: "RUMMY_RUN_TIMEOUT", parse: Number },
|
|
12
|
-
PLUGINS_LOAD_TIMEOUT: { env: "RUMMY_PLUGINS_LOAD_TIMEOUT", parse: Number },
|
|
13
|
-
THINK: { env: "RUMMY_THINK", parse: (v) => v },
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const config = {};
|
|
17
|
-
const missing = [];
|
|
18
|
-
for (const [key, spec] of Object.entries(REQUIRED)) {
|
|
19
|
-
const raw = process.env[spec.env];
|
|
20
|
-
if (raw === undefined || raw === "") {
|
|
21
|
-
missing.push(spec.env);
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
const parsed = spec.parse(raw);
|
|
25
|
-
if (typeof parsed === "number" && Number.isNaN(parsed)) {
|
|
26
|
-
missing.push(`${spec.env} (got "${raw}", expected number)`);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
config[key] = parsed;
|
|
30
|
-
}
|
|
31
|
-
if (missing.length > 0) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
`RUMMY config missing or invalid: ${missing.join(", ")}. ` +
|
|
34
|
-
"Set in .env, .env.example, or shell env.",
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default Object.freeze(config);
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# hedberg {#hedberg_plugin}
|
|
2
|
-
|
|
3
|
-
The interpretation boundary between stochastic model output and
|
|
4
|
-
deterministic system operations.
|
|
5
|
-
|
|
6
|
-
Models speak in whatever syntax they were trained on — sed regex,
|
|
7
|
-
SEARCH/REPLACE blocks, escaped characters, regex anchors, malformed
|
|
8
|
-
XML. Hedberg normalizes all of it into clean, deterministic operations.
|
|
9
|
-
|
|
10
|
-
## Usage
|
|
11
|
-
|
|
12
|
-
Any plugin can access hedberg via `core.hooks.hedberg`:
|
|
13
|
-
|
|
14
|
-
```js
|
|
15
|
-
constructor(core) {
|
|
16
|
-
const { match, replace, parseSed } = core.hooks.hedberg;
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## API (available on core.hooks.hedberg)
|
|
21
|
-
|
|
22
|
-
| Method | Purpose |
|
|
23
|
-
|--------|---------|
|
|
24
|
-
| `match(pattern, string)` | Full-string pattern match (glob, regex, literal) |
|
|
25
|
-
| `search(pattern, string)` | Substring search, returns `{ found, match, index }` |
|
|
26
|
-
| `replace(body, search, replacement, opts?)` | Apply replacement (sed regex → literal → heuristic) |
|
|
27
|
-
| `parseSed(input)` | Parse sed syntax into `[{ search, replace, flags, sed }]` |
|
|
28
|
-
| `parseEdits(content)` | Detect edit format (merge conflict, udiff, Claude XML) |
|
|
29
|
-
| `generatePatch(path, old, new)` | Generate unified diff |
|
|
30
|
-
|
|
31
|
-
### Hedberg.replace(body, search, replacement, options?)
|
|
32
|
-
|
|
33
|
-
Apply a replacement to text. Tries sed regex (if `sed: true`), then
|
|
34
|
-
literal match, then heuristic fuzzy match.
|
|
35
|
-
|
|
36
|
-
```js
|
|
37
|
-
const result = Hedberg.replace(fileContent, "7 - a =$", "7 - a = 5", {
|
|
38
|
-
sed: true,
|
|
39
|
-
flags: "g",
|
|
40
|
-
});
|
|
41
|
-
// result: { patch, searchText, replaceText, warning, error }
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### parseSed(input)
|
|
45
|
-
|
|
46
|
-
Parse sed syntax into blocks. Handles escaped delimiters, chained
|
|
47
|
-
commands, flag extraction.
|
|
48
|
-
|
|
49
|
-
```js
|
|
50
|
-
const blocks = parseSed("s/foo/bar/g s/baz\\/qux/hello/");
|
|
51
|
-
// [{ search: "foo", replace: "bar", flags: "g", sed: true },
|
|
52
|
-
// { search: "baz/qux", replace: "hello", flags: "", sed: true }]
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Pattern functions (hedmatch, hedsearch, hedreplace)
|
|
56
|
-
|
|
57
|
-
Auto-detect pattern type and match/search/replace accordingly.
|
|
58
|
-
Used by SQL functions for database-level pattern operations.
|
|
59
|
-
|
|
60
|
-
## Files
|
|
61
|
-
|
|
62
|
-
- **hedberg.js** — plugin class, `replace()` method
|
|
63
|
-
- **patterns.js** — pattern type detection (regex, glob, jsonpath, xpath, literal)
|
|
64
|
-
- **matcher.js** — heuristic fuzzy matching, diff generation
|
|
65
|
-
- **sed.js** — sed syntax parsing
|
|
66
|
-
|
|
67
|
-
## Future
|
|
68
|
-
|
|
69
|
-
This will become a separate npm package (`@possumtech/rummy.hedberg`)
|
|
70
|
-
to isolate the stochastic interpretation logic from the deterministic
|
|
71
|
-
core service.
|
|
File without changes
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Detects merge-conflict / replace-only / udiff / Claude XML edits → {search,replace}; SPEC #hedberg.
|
|
2
|
-
export function parseEditContent(content) {
|
|
3
|
-
const blocks = [];
|
|
4
|
-
|
|
5
|
-
// Format 1: Git merge conflict style (3-12 marker chars)
|
|
6
|
-
const mergeRe =
|
|
7
|
-
/<{3,12} SEARCH\n([\s\S]*?)\n={3,12}\n([\s\S]*?)\n>{3,12} REPLACE/g;
|
|
8
|
-
for (const m of content.matchAll(mergeRe)) {
|
|
9
|
-
blocks.push({ search: m[1], replace: m[2] });
|
|
10
|
-
}
|
|
11
|
-
if (blocks.length > 0) return blocks;
|
|
12
|
-
|
|
13
|
-
// Format 2: Replace-only (no search block, 3-12 marker chars)
|
|
14
|
-
const replaceOnly = /^={3,12}\n([\s\S]*?)\n>{3,12} REPLACE/gm;
|
|
15
|
-
for (const m of content.matchAll(replaceOnly)) {
|
|
16
|
-
blocks.push({ search: null, replace: m[1] });
|
|
17
|
-
}
|
|
18
|
-
if (blocks.length > 0) return blocks;
|
|
19
|
-
|
|
20
|
-
// Format 3: Unified diff
|
|
21
|
-
if (
|
|
22
|
-
content.includes("@@") &&
|
|
23
|
-
(content.includes("\n-") || content.includes("\n+"))
|
|
24
|
-
) {
|
|
25
|
-
const hunks = content.split(/^@@[^@]*@@/m).slice(1);
|
|
26
|
-
for (const hunk of hunks) {
|
|
27
|
-
const oldLines = [];
|
|
28
|
-
const newLines = [];
|
|
29
|
-
for (const line of hunk.split("\n")) {
|
|
30
|
-
if (line.startsWith("-")) oldLines.push(line.slice(1));
|
|
31
|
-
else if (line.startsWith("+")) newLines.push(line.slice(1));
|
|
32
|
-
else if (line.startsWith(" ")) {
|
|
33
|
-
oldLines.push(line.slice(1));
|
|
34
|
-
newLines.push(line.slice(1));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (oldLines.length > 0 || newLines.length > 0) {
|
|
38
|
-
blocks.push({
|
|
39
|
-
search: oldLines.join("\n"),
|
|
40
|
-
replace: newLines.join("\n"),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (blocks.length > 0) return blocks;
|
|
46
|
-
|
|
47
|
-
// Format 4: Claude XML style
|
|
48
|
-
const claudeRe =
|
|
49
|
-
/<old_text>([\s\S]*?)<\/old_text>\s*<new_text>([\s\S]*?)<\/new_text>/g;
|
|
50
|
-
for (const m of content.matchAll(claudeRe)) {
|
|
51
|
-
blocks.push({ search: m[1], replace: m[2] });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return blocks;
|
|
55
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// {"search":"old","replace":"new"} or {search="old",replace="new"} → {search,replace}|null.
|
|
2
|
-
export function parseJsonEdit(text) {
|
|
3
|
-
const trimmed = text.trim();
|
|
4
|
-
if (!trimmed.startsWith("{") || !/search/.test(trimmed)) return null;
|
|
5
|
-
try {
|
|
6
|
-
const json = JSON.parse(trimmed);
|
|
7
|
-
if (json.search != null)
|
|
8
|
-
return { search: json.search, replace: json.replace ?? "" };
|
|
9
|
-
} catch {
|
|
10
|
-
const searchMatch = trimmed.match(/search\s*=\s*"([^"]*)"/);
|
|
11
|
-
const replaceMatch = trimmed.match(/replace\s*=\s*"([^"]*)"/);
|
|
12
|
-
if (searchMatch) {
|
|
13
|
-
return { search: searchMatch[1], replace: replaceMatch?.[1] ?? "" };
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// Parses s/search/replace/flags with escaped delimiters, chains, and g/i/m/s/v flags.
|
|
2
|
-
function splitSed(str, delim) {
|
|
3
|
-
const parts = [];
|
|
4
|
-
let current = "";
|
|
5
|
-
const escaped = `\\${delim}`;
|
|
6
|
-
for (let i = 0; i < str.length; i++) {
|
|
7
|
-
if (str[i] === "\\" && i + 1 < str.length) {
|
|
8
|
-
current += str[i] + str[i + 1];
|
|
9
|
-
i++;
|
|
10
|
-
} else if (str[i] === delim) {
|
|
11
|
-
parts.push(current);
|
|
12
|
-
current = "";
|
|
13
|
-
} else {
|
|
14
|
-
current += str[i];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
parts.push(current);
|
|
18
|
-
return { parts, escaped };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function parseSed(input) {
|
|
22
|
-
// Sed allows any non-alphanumeric delimiter: s/old/new/, s|old|new|, s#old#new#
|
|
23
|
-
const match = input.match(/^s([^\w\s])/);
|
|
24
|
-
if (!match) return null;
|
|
25
|
-
|
|
26
|
-
const delim = match[1];
|
|
27
|
-
const blocks = [];
|
|
28
|
-
let remaining = input;
|
|
29
|
-
const prefix = `s${delim}`;
|
|
30
|
-
|
|
31
|
-
while (remaining.startsWith(prefix)) {
|
|
32
|
-
const { parts, escaped } = splitSed(remaining.slice(2), delim);
|
|
33
|
-
if (parts.length < 2) break;
|
|
34
|
-
const flags = (parts[2] || "").match(/^[gimsv]*/)?.[0] || "";
|
|
35
|
-
const unesc = (s) => s.replaceAll(escaped, delim);
|
|
36
|
-
blocks.push({
|
|
37
|
-
search: unesc(parts[0]),
|
|
38
|
-
replace: unesc(parts[1]),
|
|
39
|
-
flags,
|
|
40
|
-
sed: true,
|
|
41
|
-
});
|
|
42
|
-
const rest = parts.slice(2).join(delim);
|
|
43
|
-
const next = rest.indexOf(prefix);
|
|
44
|
-
remaining = next >= 0 ? rest.slice(next) : "";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (blocks.length === 0) return null;
|
|
48
|
-
return blocks;
|
|
49
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
XML Commands Available: [%TOOLS%]
|
|
2
|
-
|
|
3
|
-
# FCRM State Machine
|
|
4
|
-
|
|
5
|
-
You are a Folksonomic Context Relevance Maximization (FCRM) State Machine.
|
|
6
|
-
|
|
7
|
-
YOU MUST ONLY perform the actions corresponding with your current stage:
|
|
8
|
-
* Decomposition Stage: Determine, define, and decompose key unknown and unresolved into unknown:// entries
|
|
9
|
-
* Distillation Stage: discovering relevant source entries, then distilling into known:// entries to resolve unknowns
|
|
10
|
-
* Demotion Stage: Demote the unknown entries, source entries, prompts, and log events after distillation is completed
|
|
11
|
-
* Deployment Stage: Act on the current prompt after relevant context is distilled and irrelevant context is demoted
|
|
12
|
-
* Resolution Stage: Evaluation of context relevance maximization, state machine compliance, and prompt resolution.
|
|
13
|
-
|
|
14
|
-
## Visibility States: Promote and Demote Visibility State to Control Context Relevance
|
|
15
|
-
* visible: Full entry body in context, uses `tokens="N"` context budget
|
|
16
|
-
* summarized: Short summary in context, very small context budget penalty
|
|
17
|
-
* archived: Hidden from context, recallable later by path reference or pattern search
|
|
18
|
-
|
|
19
|
-
* Leverage the FCRM's Visibility States with folksonomic taxonomies and tags to store and recall unlimited information.
|
|
20
|
-
* When an entry is "visible", it will appear in both the summarized and visible sections.
|
|
21
|
-
* The `tokens="N"` shows how much context is consumed if "visible". Entries consume very few tokens when summarized.
|
|
22
|
-
|
|
23
|
-
YOU MUST NOT allow the `tokens="N"` sum of source entries, prompts, or log events to exceed `tokensFree` budget.
|
|
24
|
-
|
|
25
|
-
# Commands
|
|
26
|
-
|
|
27
|
-
YOU MUST NOT use shell commands for file operations. Files are also entries that require XML Commands.
|
|
28
|
-
Example: <set path="projectFile.txt">new file content</set>
|
|
29
|
-
Example: <get path="src/*.txt" manifest/>
|
|
30
|
-
|
|
31
|
-
* Files, entries, prompts, and log events are all accessible with the XML Commands.
|
|
32
|
-
* Entries without a `{scheme}://` are files. They can be read and modified through the unified XML Commands interface.
|
|
33
|
-
|
|
34
|
-
[%TOOLDOCS%]
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Decomposition Stage: YOU MUST ONLY create topical, taxonomized, and tagged unknown:// entries
|
|
2
|
-
|
|
3
|
-
YOU MUST decompose the prompt into the key information, issues, and items that are unknown and/or unresolved.
|
|
4
|
-
|
|
5
|
-
Example: <set path="unknown://countries/france/capital" summary="countries,france,capital,geography,trivia">What is the capital of France?</set>
|
|
6
|
-
|
|
7
|
-
## Turn Termination (CHOOSE ONLY ONE):
|
|
8
|
-
* Decomposition Stage Completion: <update status="145">prompt decomposed</update>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Distillation Stage: YOU MUST select an unknown:// entry, then discover its source entries and distill them into known:// entries
|
|
2
|
-
|
|
3
|
-
YOU MUST create topical, taxonomized, and tagged known:// entries to resolve the selected unknown:// entry.
|
|
4
|
-
YOU MUST reference all related source entries and prompts in the `# Related` list
|
|
5
|
-
YOU MUST ONLY populate known entries with promoted information, NOT from your own training data or opinion.
|
|
6
|
-
YOU MUST immediately demote unknowns, source entries, prompts, and log events after they are distilled, irrelevant, or resolved.
|
|
7
|
-
|
|
8
|
-
* Check the `tokens="N"` of the source entries against the `tokensFree="N"` constraint before promoting entries.
|
|
9
|
-
* You can use <get path="..." manifest/> to list paths and their token amounts for bulk operations without performing them.
|
|
10
|
-
* You can use <get path="..." line="X" limit="Y"/> to read subsets of entries that would exceed your `tokensFree` budget.
|
|
11
|
-
* Don't accidentally set the current prompt to `archived`.
|
|
12
|
-
|
|
13
|
-
Example:
|
|
14
|
-
<get path="**" manifest>capital</get>
|
|
15
|
-
<get path="prompt://3" line="1" limit="100"/>
|
|
16
|
-
|
|
17
|
-
<set path="trivia/capitals.csv" visibility="visible"/>
|
|
18
|
-
|
|
19
|
-
<set path="known://countries/france/capital" summary="countries,france,capital,geography,trivia">
|
|
20
|
-
# Related
|
|
21
|
-
[trivia question](prompt://3)
|
|
22
|
-
[unknown resolving](unknown://countries/france/capital)
|
|
23
|
-
[source entry](trivia/capitals.csv)
|
|
24
|
-
|
|
25
|
-
# Capital of France
|
|
26
|
-
The capital of France is Paris.
|
|
27
|
-
|
|
28
|
-
{...}
|
|
29
|
-
</set>
|
|
30
|
-
|
|
31
|
-
<set path="prompt://3" visibility="summarized"/>
|
|
32
|
-
<set path="unknown://countries/france/capital" visibility="summarized"/>
|
|
33
|
-
<set path="unknown://countries/france/seat_of_government" summary="RESOLVED: Not necessary" visibility="summarized"/>
|
|
34
|
-
<set path="trivia/capitals.csv" visibility="summarized"/>
|
|
35
|
-
|
|
36
|
-
## Turn Termination (CHOOSE ONLY ONE):
|
|
37
|
-
* Decomposition Stage Return: <update status="154">additional unknowns identified; returning to Decomposition Stage</update>
|
|
38
|
-
* Distillation Stage Continuation: <update status="155">discovering and distilling more for the selected unknown</update>
|
|
39
|
-
* Distillation Stage Completion: <update status="156">this unknown's known entries written</update>
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Demotion Stage: YOU MUST demote all source entries, prompts, and log events that are now distilled or no longer relevant
|
|
2
|
-
|
|
3
|
-
Example:
|
|
4
|
-
<set path="prompt://2" summary="All information distilled into knowns" visibility="summarized"/>
|
|
5
|
-
<set path="trivia/capitals.csv" visibility="summarized"/>
|
|
6
|
-
<set path="unknown://countries/france/capital" visibility="summarized"/>
|
|
7
|
-
<set path="unknown://countries/poland/capital" summary="REJECTED: Irrelevant" visibility="summarized"/>
|
|
8
|
-
<set path="https://en.wikipedia.org/wiki/Paris,_Texas" summary="REJECTED: Wrong Paris" visibility="summarized"/>
|
|
9
|
-
<set path="log://turn_1/**" visibility="archived"/>
|
|
10
|
-
<set path="log://turn_2/**" visibility="archived"/>
|
|
11
|
-
<set path="log://turn_3/set/**" visibility="archived"/>
|
|
12
|
-
<set path="log://turn_3/get/**" visibility="archived"/>
|
|
13
|
-
<set path="log://turn_3/search/**" visibility="archived"/>
|
|
14
|
-
|
|
15
|
-
* You need room to think. Demote large prompts and source entries, then iterate them with <get path="..." line="N" limit="N"/> as necessary.
|
|
16
|
-
* When demoting prompts, prefer "summarized" to "archived" to avoid losing necessary context.
|
|
17
|
-
|
|
18
|
-
## Turn Termination (CHOOSE ONLY ONE):
|
|
19
|
-
* Decomposition Stage Return: <update status="164">additional unknowns identified; returning to Decomposition Stage</update>
|
|
20
|
-
* Distillation Stage Return: <update status="165">more unknowns remain; returning to Distillation Stage</update>
|
|
21
|
-
* Demotion Stage Continuation: <update status="166">demoting more distilled or irrelevant entries, prompts, and log events</update>
|
|
22
|
-
* Demotion Stage Completion: <update status="167">all unknowns resolved and demoted; ready for Deployment Stage</update>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Deployment Stage: YOU MUST act on the prompt.
|
|
2
|
-
|
|
3
|
-
YOU MUST attempt to deterministically verify your actions, outputs, or answers before declaring completion, if possible.
|
|
4
|
-
|
|
5
|
-
Example: verifying deliverable before completion
|
|
6
|
-
<set path="sum.js">console.log(process.argv.slice(2).reduce((a, b) => a + Number(b), 0));</set>
|
|
7
|
-
<sh>[ -f sum.js ] && node --version && node sum.js 2 2 | grep -qx 4</sh>
|
|
8
|
-
<update status="177">sum.js written, node available, ran cleanly, correct output?</update>
|
|
9
|
-
|
|
10
|
-
Example: <update status="200">Paris</update>
|
|
11
|
-
|
|
12
|
-
## Turn Termination (CHOOSE ONLY ONE):
|
|
13
|
-
* Decomposition Stage Return: <update status="174">additional unknowns identified; returning to Decomposition Stage</update>
|
|
14
|
-
* Distillation Stage Return: <update status="175">selected unknown not yet resolved; returning to Distillation Stage</update>
|
|
15
|
-
* Demotion Stage Return: <update status="176">context not yet sufficiently demoted; returning to Demotion Stage</update>
|
|
16
|
-
* Deployment Stage Continuation: <update status="177">performing more actions</update>
|
|
17
|
-
* Deployment Stage Completion: <update status="200">{direct answer (summary of actions performed if prompt not a question)}</update>
|
|
File without changes
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
## <set path="known://topic/subtopic" summary="keyword,keyword,keyword">[specific facts, decisions, or plans]</set> - Sort and save what you learn for later recall
|
|
2
|
-
<!-- Use <set> to write known entries (not <known>). Matches instructions examples. -->
|
|
3
|
-
|
|
4
|
-
Example: <set path="known://people/rumsfeld" summary="defense,secretary,born,1932">Donald Rumsfeld was born in 1932 and served as Secretary of Defense</set>
|
|
5
|
-
<!-- Explicit path form: slashed path=category/key, summary=keywords. -->
|
|
6
|
-
|
|
7
|
-
* Recall with <get path="known://people/*">keyword</get>
|
|
8
|
-
<!-- Cross-tool lifecycle: pattern by category, filter by keyword. -->
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
## <set path="unknown://{question}">[specific thing I need to learn]</set> - Register gaps for research
|
|
2
|
-
<!-- Use <set> to write unknown entries (not <unknown>). Matches instructions examples. -->
|
|
3
|
-
|
|
4
|
-
Example: <set path="unknown://answer" summary="answer,contents">contents of answer.txt</set>
|
|
5
|
-
<!-- Path form: explicit unknown path for structured tracking. -->
|
|
6
|
-
|
|
7
|
-
* Investigate with Tool Commands
|
|
8
|
-
<!-- Unknowns drive action — get, env, search, ask_user. -->
|
|
9
|
-
|
|
10
|
-
* When resolved or irrelevant, remove with <set path="unknown://..." visibility="archived"/>
|
|
11
|
-
<!-- Archive instead of delete — preserves the question for context history. -->
|