@inetafrica/open-claudia 2.6.37 → 2.6.39
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/bin/cli.js +18 -0
- package/bin/ideas.js +69 -0
- package/bin/keyring.js +64 -0
- package/bin/lessons.js +72 -0
- package/bin/pack.js +45 -2
- package/bot.js +8 -0
- package/core/actions.js +4 -2
- package/core/config.js +10 -1
- package/core/day-seeds.js +98 -0
- package/core/dream.js +413 -18
- package/core/handlers.js +137 -8
- package/core/ideas.js +114 -0
- package/core/keyring.js +79 -0
- package/core/lessons.js +276 -0
- package/core/pack-review.js +139 -17
- package/core/packs.js +95 -2
- package/core/recall/graph.js +17 -0
- package/core/recall/index.js +12 -5
- package/core/redact.js +25 -3
- package/core/runner.js +69 -3
- package/core/state.js +3 -0
- package/core/subagent.js +20 -4
- package/core/system-prompt.js +39 -0
- package/package.json +11 -3
- package/test-abilities.js +53 -0
- package/test-ability-couse.js +68 -0
- package/test-ability-extraction.js +109 -0
- package/test-ability-merge-guard.js +42 -0
- package/test-ability-tiers.js +57 -0
- package/test-ability-transfer.js +70 -0
- package/test-learning-e2e.js +98 -0
- package/test-project-transcripts-smoke.js +50 -0
- package/test-recall-engine.js +7 -5
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// PROOF: an ability learned while working on one project surfaces when working
|
|
2
|
+
// on a DIFFERENT project — the cross-session / cross-project reuse the learning
|
|
3
|
+
// system is built on. Runs against the REAL recall graph (temp db, stub corpus),
|
|
4
|
+
// using the real default tunables. No reimplementation, no rigged thresholds.
|
|
5
|
+
const assert = require("assert");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "ability-transfer-"));
|
|
11
|
+
process.env.RECALL_GRAPH_DB = path.join(tmp, "graph.db");
|
|
12
|
+
|
|
13
|
+
const graph = require("./core/recall/graph");
|
|
14
|
+
if (!graph.available()) { console.log("ability transfer OK (skipped — no node:sqlite)"); process.exit(0); }
|
|
15
|
+
|
|
16
|
+
// Corpus:
|
|
17
|
+
// - chat-mobile, spaces : two projects that both ship a mobile app.
|
|
18
|
+
// - billing : a project that does NOT (negative control).
|
|
19
|
+
// - mobile-app-deploy : a reusable ABILITY (kind:"ability") extracted from
|
|
20
|
+
// the first deploy; records where it was learned.
|
|
21
|
+
// Projects point at the ability via [[mobile-app-deploy]] in their body.
|
|
22
|
+
const ABILITY_STANCE =
|
|
23
|
+
"Reusable how-to. Ship a mobile app: bump versionCode, build the APK, push the in-app updater. learned_on: chat-mobile (2026-06).";
|
|
24
|
+
const packsStub = {
|
|
25
|
+
listPacks: () => [
|
|
26
|
+
{ dir: "chat-mobile", name: "Chat Mobile", tags: [], parent: null,
|
|
27
|
+
sections: { Stance: "Spaces chat client. Ship via [[mobile-app-deploy]].", Procedure: "", State: "", Journal: "" } },
|
|
28
|
+
{ dir: "spaces", name: "Spaces", tags: [], parent: null,
|
|
29
|
+
sections: { Stance: "Spaces app. Releases follow [[mobile-app-deploy]].", Procedure: "", State: "", Journal: "" } },
|
|
30
|
+
{ dir: "billing", name: "Billing", tags: [], parent: null,
|
|
31
|
+
sections: { Stance: "Invoices and payments. No mobile build.", Procedure: "", State: "", Journal: "" } },
|
|
32
|
+
{ dir: "mobile-app-deploy", name: "Mobile App Deploy", tags: [], parent: null, kind: "ability",
|
|
33
|
+
sections: { Stance: ABILITY_STANCE, Procedure: "1. bump versionCode 2. build APK 3. in-app updater", State: "", Journal: "" } },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
const entStub = { listEntities: () => [] };
|
|
37
|
+
|
|
38
|
+
// Structural sync derives governed-by edges from [[links]] → shared concern.
|
|
39
|
+
graph.syncFromCorpus(packsStub, entStub);
|
|
40
|
+
const edges = graph.allEdges();
|
|
41
|
+
assert.ok(edges.some((e) => e.src === "pack:chat-mobile" && e.dst === "pack:mobile-app-deploy" && e.type === "governed-by"),
|
|
42
|
+
"chat-mobile is governed-by the ability");
|
|
43
|
+
assert.ok(edges.some((e) => e.src === "pack:spaces" && e.dst === "pack:mobile-app-deploy" && e.type === "governed-by"),
|
|
44
|
+
"spaces is governed-by the ability");
|
|
45
|
+
|
|
46
|
+
// THE PROOF: working on `spaces` — a project the ability was NOT learned on —
|
|
47
|
+
// surfaces the ability via spreading activation (default tunables, no rigging).
|
|
48
|
+
const fromSpaces = graph.expand([{ id: "pack:spaces", score: 4 }], {});
|
|
49
|
+
assert.ok(fromSpaces.has("pack:mobile-app-deploy"),
|
|
50
|
+
"ability surfaces when working on spaces (cross-project reuse)");
|
|
51
|
+
|
|
52
|
+
// It also surfaces from its origin project, and even for a weak seed.
|
|
53
|
+
const fromChat = graph.expand([{ id: "pack:chat-mobile", score: 4 }], {});
|
|
54
|
+
assert.ok(fromChat.has("pack:mobile-app-deploy"), "ability surfaces from its origin project");
|
|
55
|
+
const weakSeed = graph.expand([{ id: "pack:spaces", score: 1 }], {});
|
|
56
|
+
assert.ok(weakSeed.has("pack:mobile-app-deploy"), "surfaces even for a weak seed (threshold scales with seed)");
|
|
57
|
+
|
|
58
|
+
// Negative control: an unrelated project does NOT pull the ability in.
|
|
59
|
+
const fromBilling = graph.expand([{ id: "pack:billing", score: 4 }], {});
|
|
60
|
+
assert.ok(!fromBilling.has("pack:mobile-app-deploy"),
|
|
61
|
+
"unlinked project does NOT surface the ability (it's the link doing the work, not noise)");
|
|
62
|
+
|
|
63
|
+
// Provenance lives on the ability node itself, ready to show the user.
|
|
64
|
+
assert.ok(/learned_on:\s*chat-mobile/.test(ABILITY_STANCE), "ability records where it was learned");
|
|
65
|
+
|
|
66
|
+
console.log("ability transfer OK — a learned-once pattern surfaces across projects");
|
|
67
|
+
console.log(" working on spaces → graph pulls in:", [...fromSpaces.keys()].join(", ") || "(none)");
|
|
68
|
+
console.log(" working on chat → graph pulls in:", [...fromChat.keys()].join(", ") || "(none)");
|
|
69
|
+
console.log(" working on billing → graph pulls in:", [...fromBilling.keys()].join(", ") || "(none)");
|
|
70
|
+
console.log(" provenance on ability:", ABILITY_STANCE.match(/learned_on:[^.]*/)[0]);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// CAPSTONE PROOF of the goal: "feel like it remembers and reuses ideas/patterns/
|
|
2
|
+
// abilities across sessions and projects, like it is learning and improving."
|
|
3
|
+
// One narrative walks the WHOLE lifecycle against real code — reviewer extraction,
|
|
4
|
+
// FTS router, recall graph, the co-use loop, and the dream — no reimplementation.
|
|
5
|
+
//
|
|
6
|
+
// Stage 1 capture — a reusable how-to done on project A is extracted into an ability
|
|
7
|
+
// Stage 2 recall@home — the ability is linked to A and surfaces while working on A
|
|
8
|
+
// Stage 3 cold-start — on a BRAND-NEW project B it surfaces by ACTIVITY (FTS), no edge yet
|
|
9
|
+
// Stage 4 transfer — using it on B records reuse, the edge forms, B now surfaces it via the graph
|
|
10
|
+
// Stage 5 graduation — once reused widely, the dream promotes it to always-on
|
|
11
|
+
// Stage 6 provenance — it can always say where it was learned and where it has been applied
|
|
12
|
+
const assert = require("assert");
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
|
|
17
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "learning-e2e-"));
|
|
18
|
+
process.env.PACKS_DIR = path.join(tmp, "packs");
|
|
19
|
+
process.env.RECALL_GRAPH_DB = path.join(tmp, "graph.db");
|
|
20
|
+
|
|
21
|
+
const packs = require("./core/packs");
|
|
22
|
+
const graph = require("./core/recall/graph");
|
|
23
|
+
const review = require("./core/pack-review");
|
|
24
|
+
const dream = require("./core/dream");
|
|
25
|
+
const entStub = { listEntities: () => [] };
|
|
26
|
+
const haveGraph = graph.available();
|
|
27
|
+
|
|
28
|
+
// Three real projects exist as context packs over time.
|
|
29
|
+
packs.createPack({ dir: "spaces", name: "Spaces", description: "the Spaces web+mobile app" });
|
|
30
|
+
packs.createPack({ dir: "chat-mobile", name: "Chat Mobile", description: "the Spaces chat client" });
|
|
31
|
+
packs.createPack({ dir: "field-app", name: "Field App", description: "the field-ops companion app" });
|
|
32
|
+
|
|
33
|
+
// reindex() returns false when node:sqlite is unavailable — use it as the FTS gate.
|
|
34
|
+
const haveFts = packs.reindex();
|
|
35
|
+
|
|
36
|
+
// ── Stage 1: CAPTURE. A turn ships a mobile build on `spaces`; the reviewer
|
|
37
|
+
// extracts the reusable how-to as an ability with activity-oriented metadata.
|
|
38
|
+
const cap = review.applyAction({
|
|
39
|
+
action: "create", dir: "mobile-app-deploy", name: "Mobile App Deploy",
|
|
40
|
+
description: "Ship a mobile app: bump versionCode, build the APK, push the in-app updater",
|
|
41
|
+
tags: ["mobile", "deploy", "apk", "versioncode", "release"],
|
|
42
|
+
kind: "ability", learned_on: "spaces", applied_on: ["spaces"],
|
|
43
|
+
procedure: "1. bump versionCode 2. build APK 3. push in-app updater",
|
|
44
|
+
journal: "Shipped the spaces mobile build.",
|
|
45
|
+
});
|
|
46
|
+
assert.ok(cap.ability && cap.learned_on === "spaces", "Stage 1: ability captured, learned on spaces");
|
|
47
|
+
|
|
48
|
+
if (haveFts) packs.reindex();
|
|
49
|
+
if (haveGraph) graph.syncFromCorpus(packs, entStub);
|
|
50
|
+
|
|
51
|
+
// ── Stage 2: RECALL AT HOME. Link formed; working on spaces surfaces it.
|
|
52
|
+
if (haveGraph) {
|
|
53
|
+
const fromSpaces = graph.expand([{ id: "pack:spaces", score: 4 }], {});
|
|
54
|
+
assert.ok(fromSpaces.has("pack:mobile-app-deploy"), "Stage 2: ability surfaces on its origin project");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Stage 3: COLD-START on a NEW project. No graph edge to chat-mobile yet, but
|
|
58
|
+
// the ability is discoverable by the WORK being done (activity-term FTS) — the
|
|
59
|
+
// crucial "I've done something like this before" moment in a new context.
|
|
60
|
+
if (haveGraph) {
|
|
61
|
+
const fromChatNoEdge = graph.expand([{ id: "pack:chat-mobile", score: 4 }], {});
|
|
62
|
+
assert.ok(!fromChatNoEdge.has("pack:mobile-app-deploy"), "Stage 3: no graph edge to the new project yet");
|
|
63
|
+
}
|
|
64
|
+
if (haveFts) {
|
|
65
|
+
const hits = packs.matchPacks("ship the chat mobile app: bump versionCode and build the apk", { limit: 5 });
|
|
66
|
+
assert.ok(hits.some((h) => h.dir === "mobile-app-deploy"),
|
|
67
|
+
"Stage 3: ability surfaces in a brand-new project via activity match, before any edge");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Stage 4: TRANSFER ON USE. The agent opens the ability + chat-mobile together
|
|
71
|
+
// (the co-use signal). recordCoUse grows applied_on, the edge forms on sync,
|
|
72
|
+
// and chat-mobile now surfaces it through the graph too.
|
|
73
|
+
const transferred = packs.recordCoUse(["pack:mobile-app-deploy", "pack:chat-mobile"]);
|
|
74
|
+
assert.deepStrictEqual(transferred.map((t) => t.project), ["chat-mobile"], "Stage 4: reuse recorded from real use");
|
|
75
|
+
assert.deepStrictEqual(packs.readPack("mobile-app-deploy").applied_on, ["spaces", "chat-mobile"], "Stage 4: applied_on grew");
|
|
76
|
+
if (haveGraph) {
|
|
77
|
+
graph.syncFromCorpus(packs, entStub);
|
|
78
|
+
const fromChat = graph.expand([{ id: "pack:chat-mobile", score: 4 }], {});
|
|
79
|
+
assert.ok(fromChat.has("pack:mobile-app-deploy"), "Stage 4: new project now surfaces it via the graph");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Stage 5: GRADUATION. Reused on a third project, the dream promotes it to the
|
|
83
|
+
// always-on tier so it is offered everywhere, not just on matched projects.
|
|
84
|
+
packs.recordCoUse(["pack:mobile-app-deploy", "pack:field-app"]);
|
|
85
|
+
assert.strictEqual(packs.readPack("mobile-app-deploy").applied_on.length, 3, "Stage 5: applied across three projects");
|
|
86
|
+
const tierLines = dream.manageAbilityTiers();
|
|
87
|
+
assert.strictEqual(packs.readPack("mobile-app-deploy").skill, true, "Stage 5: dream promoted it to always-on");
|
|
88
|
+
assert.ok(tierLines.some((l) => /Promoted .*Mobile App Deploy/.test(l)), "Stage 5: promotion announced");
|
|
89
|
+
assert.ok(packs.listSkillPacks().some((p) => p.dir === "mobile-app-deploy"), "Stage 5: now in the always-on skill index");
|
|
90
|
+
|
|
91
|
+
// ── Stage 6: PROVENANCE. It can always say where it was learned and applied.
|
|
92
|
+
const ab = packs.readPack("mobile-app-deploy");
|
|
93
|
+
assert.strictEqual(ab.learned_on, "spaces", "Stage 6: remembers where it was learned");
|
|
94
|
+
assert.deepStrictEqual(ab.applied_on, ["spaces", "chat-mobile", "field-app"], "Stage 6: remembers everywhere it transferred");
|
|
95
|
+
|
|
96
|
+
const skips = [!haveGraph && "graph", !haveFts && "fts"].filter(Boolean);
|
|
97
|
+
console.log(`learning E2E OK — capture → recall → cold-start → transfer → graduation → provenance${skips.length ? ` (skipped: ${skips.join(", ")} — no node:sqlite)` : ""}`);
|
|
98
|
+
console.log(" learned on:", ab.learned_on, "| applied on:", ab.applied_on.join(", "), "| always-on:", ab.skill);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const assert = require("assert");
|
|
5
|
+
const { ProjectTranscripts, normalizeProjectPath, projectHash } = require("./project-transcripts");
|
|
6
|
+
|
|
7
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "open-claudia-transcripts-"));
|
|
8
|
+
const project = path.join(tmp, "repo");
|
|
9
|
+
fs.mkdirSync(project);
|
|
10
|
+
|
|
11
|
+
const transcripts = new ProjectTranscripts({
|
|
12
|
+
configDir: tmp,
|
|
13
|
+
maxEntryChars: 100,
|
|
14
|
+
redact: (value) => String(value).replace(/SECRET/g, "[REDACTED]"),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const normalized = normalizeProjectPath(project);
|
|
18
|
+
const hash = projectHash(project);
|
|
19
|
+
assert.strictEqual(hash, projectHash(normalized));
|
|
20
|
+
|
|
21
|
+
const result = transcripts.append({
|
|
22
|
+
role: "user",
|
|
23
|
+
text: "hello SECRET " + "x".repeat(100),
|
|
24
|
+
userId: "telegram:1",
|
|
25
|
+
chat: { transport: "telegram", id: "1" },
|
|
26
|
+
projectName: "repo",
|
|
27
|
+
projectPath: project,
|
|
28
|
+
backend: "codex",
|
|
29
|
+
sessionId: "sess-1",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.ok(result.transcriptPath.startsWith(path.join(tmp, "transcripts")));
|
|
33
|
+
assert.ok(fs.existsSync(result.transcriptPath));
|
|
34
|
+
const line = fs.readFileSync(result.transcriptPath, "utf8").trim();
|
|
35
|
+
const entry = JSON.parse(line);
|
|
36
|
+
assert.strictEqual(entry.project.path, normalized);
|
|
37
|
+
// The stored hash is per-user (projectHash seeds with userId), so verify it
|
|
38
|
+
// against the same user the entry was written for — not the path-only hash above.
|
|
39
|
+
assert.strictEqual(entry.project.hash, projectHash(normalized, "telegram:1"));
|
|
40
|
+
assert.strictEqual(entry.backend, "codex");
|
|
41
|
+
assert.ok(entry.truncated);
|
|
42
|
+
assert.ok(!entry.text.includes("SECRET"));
|
|
43
|
+
assert.ok(entry.text.includes("[REDACTED]"));
|
|
44
|
+
|
|
45
|
+
const note = transcripts.buildPointerNote(project, "repo", "telegram:1");
|
|
46
|
+
assert.ok(note.includes(result.transcriptPath));
|
|
47
|
+
assert.ok(note.includes("Do not read the whole file"));
|
|
48
|
+
|
|
49
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
50
|
+
console.log("project-transcripts smoke OK");
|
package/test-recall-engine.js
CHANGED
|
@@ -3,18 +3,20 @@ const assert = require("assert");
|
|
|
3
3
|
const recall = require("./core/recall");
|
|
4
4
|
|
|
5
5
|
// --- selector ---
|
|
6
|
-
assert.strictEqual(recall.
|
|
7
|
-
assert.strictEqual(recall.activeEngineName({
|
|
8
|
-
assert.strictEqual(recall.activeEngineName({ recallEngine:
|
|
6
|
+
assert.strictEqual(recall.DEFAULT_ENGINE, "discoverer", "discoverer is the product default");
|
|
7
|
+
assert.strictEqual(recall.activeEngineName({}), "discoverer", "no setting → default (discoverer)");
|
|
8
|
+
assert.strictEqual(recall.activeEngineName({ recallEngine: null }), "discoverer", "null setting → default");
|
|
9
|
+
assert.strictEqual(recall.activeEngineName({ recallEngine: "classic" }), "classic", "classic still selectable as opt-out");
|
|
10
|
+
assert.strictEqual(recall.activeEngineName({ recallEngine: "nope" }), "discoverer", "unknown → default");
|
|
9
11
|
assert.strictEqual(recall.activeEngineName({ recallEngine: "CLASSIC" }), "classic", "case-insensitive");
|
|
10
12
|
assert.ok(recall.listEngines().includes("classic"));
|
|
11
13
|
assert.strictEqual(typeof recall.getEngine("classic").run, "function");
|
|
12
|
-
assert.strictEqual(recall.getEngine("bogus").name, "classic", "getEngine
|
|
14
|
+
assert.strictEqual(recall.getEngine("bogus").name, "classic", "getEngine hard-floor stays classic (crash-proof)");
|
|
13
15
|
|
|
14
16
|
// env override when no setting
|
|
15
17
|
const prev = process.env.RECALL_ENGINE;
|
|
16
18
|
process.env.RECALL_ENGINE = "classic";
|
|
17
|
-
assert.strictEqual(recall.activeEngineName(null), "classic");
|
|
19
|
+
assert.strictEqual(recall.activeEngineName(null), "classic", "env var overrides the default when no per-channel setting");
|
|
18
20
|
if (prev === undefined) delete process.env.RECALL_ENGINE; else process.env.RECALL_ENGINE = prev;
|
|
19
21
|
|
|
20
22
|
// --- classic engine orchestration: calls helpers and returns their blocks ---
|