@inetafrica/open-claudia 2.6.24 → 2.6.25
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/pack.js +3 -0
- package/core/dream.js +7 -6
- package/core/pack-review.js +5 -5
- package/core/packs.js +82 -5
- package/core/runner.js +7 -0
- package/package.json +1 -1
package/bin/pack.js
CHANGED
|
@@ -56,6 +56,9 @@ function run(args) {
|
|
|
56
56
|
const p = packs.findPack(rest[0]);
|
|
57
57
|
if (!p) { console.error(`No pack: ${rest[0]}`); process.exitCode = 1; return; }
|
|
58
58
|
console.log(fs.readFileSync(path.join(packs.PACKS_DIR, p.dir, "PACK.md"), "utf-8"));
|
|
59
|
+
const prov = packs.readProvenance(p.dir);
|
|
60
|
+
const authored = packs.SECTIONS.map((s) => `${s}=${prov.sections[s] || "user"}`).join(" ");
|
|
61
|
+
console.log(`\n# provenance: ${authored}${prov.lastWriter ? ` (last: ${prov.lastWriter} ${prov.ts})` : ""}`);
|
|
59
62
|
break;
|
|
60
63
|
}
|
|
61
64
|
|
package/core/dream.js
CHANGED
|
@@ -62,7 +62,7 @@ function buildDreamPrompt() {
|
|
|
62
62
|
`description: ${p.description}`,
|
|
63
63
|
`tags: ${p.tags.join(", ")}`,
|
|
64
64
|
`last_used: ${p.last_used || "never"} (used ${p.usage_count || 0}×)`,
|
|
65
|
-
`Stance: ${p.sections.Stance}`,
|
|
65
|
+
`Stance${packs.provenanceOf(p.dir, "Stance") === "user" ? " (USER-AUTHORED — do not rewrite)" : ""}: ${p.sections.Stance}`,
|
|
66
66
|
`Procedure: ${p.sections.Procedure}`,
|
|
67
67
|
`State: ${p.sections.State}`,
|
|
68
68
|
`Journal:\n${p.sections.Journal}`,
|
|
@@ -93,7 +93,7 @@ ${persona.loadPersona()}
|
|
|
93
93
|
|
|
94
94
|
Your job — decide what consolidation, if any, is warranted:
|
|
95
95
|
|
|
96
|
-
1. merges: packs that are clearly the SAME topic under different names get merged into one. Supply the merged Stance/Procedure/State (synthesised, not concatenated; null leaves the target's section alone) and a one-sentence journal note. Be conservative: when in doubt, do not merge.
|
|
96
|
+
1. merges: packs that are clearly the SAME topic under different names get merged into one. Supply the merged Stance/Procedure/State (synthesised, not concatenated; null leaves the target's section alone) and a one-sentence journal note. Be conservative: when in doubt, do not merge. A Stance marked USER-AUTHORED is off-limits — leave its stance null; the system preserves it verbatim regardless.
|
|
97
97
|
2. umbrellas: when 3+ packs are siblings under one theme, create an umbrella pack whose State is a 3-6 line map of the family ("for X see pack Y"), and list its children. The umbrella is a router, not a duplicate.
|
|
98
98
|
3. parents: assign an existing pack as parent of another (sub-topic relationship) without creating anything.
|
|
99
99
|
4. retag: tighten descriptions and tags. The router FTS-matches incoming messages against name/description/tags, so generic words there cause false matches. Descriptions should be one specific line; tags specific nouns.
|
|
@@ -180,18 +180,19 @@ function applyDream(decision, backupRoot) {
|
|
|
180
180
|
const into = m?.into && packs.readPack(m.into);
|
|
181
181
|
const from = [].concat(m?.from || []).filter((d) => d && d !== m.into && !gone.has(d) && packs.readPack(d));
|
|
182
182
|
if (!into || from.length === 0) continue;
|
|
183
|
-
packs.updatePack(m.into, {
|
|
183
|
+
const mr = packs.updatePack(m.into, {
|
|
184
184
|
stance: typeof m.stance === "string" ? m.stance : "",
|
|
185
185
|
procedure: typeof m.procedure === "string" ? m.procedure : "",
|
|
186
186
|
state: typeof m.state === "string" ? m.state : "",
|
|
187
187
|
journal: m.journal || `absorbed ${from.join(", ")}`,
|
|
188
|
-
});
|
|
188
|
+
}, "dream");
|
|
189
189
|
for (const d of from) {
|
|
190
190
|
backupPack(d, backupRoot);
|
|
191
191
|
packs.removePack(d);
|
|
192
192
|
gone.add(d);
|
|
193
193
|
}
|
|
194
194
|
lines.push(`📦 Merged ${from.join(" + ")} into ${m.into}`);
|
|
195
|
+
if (mr.protectedStance) lines.push(`🛡️ Kept ${m.into}'s user-authored Stance as-is`);
|
|
195
196
|
} catch (e) { console.warn(`[dream] merge failed: ${e.message}`); }
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -209,7 +210,7 @@ function applyDream(decision, backupRoot) {
|
|
|
209
210
|
tags: Array.isArray(u.tags) ? u.tags.slice(0, 6) : [],
|
|
210
211
|
state: u.state || "",
|
|
211
212
|
journal: `umbrella created over ${children.join(", ")}`,
|
|
212
|
-
});
|
|
213
|
+
}, "dream");
|
|
213
214
|
}
|
|
214
215
|
for (const c of children) {
|
|
215
216
|
const child = packs.readPack(c);
|
|
@@ -241,7 +242,7 @@ function applyDream(decision, backupRoot) {
|
|
|
241
242
|
const desc = typeof r.description === "string" ? r.description.trim() : "";
|
|
242
243
|
const tags = Array.isArray(r.tags) ? r.tags.map((t) => String(t).trim()).filter(Boolean).slice(0, 6) : [];
|
|
243
244
|
if (!desc && tags.length === 0) continue;
|
|
244
|
-
packs.updatePack(r.pack, { description: desc, tags });
|
|
245
|
+
packs.updatePack(r.pack, { description: desc, tags }, "dream");
|
|
245
246
|
lines.push(`🏷️ Sharpened ${r.pack}'s description/tags`);
|
|
246
247
|
} catch (e) { console.warn(`[dream] retag failed: ${e.message}`); }
|
|
247
248
|
}
|
package/core/pack-review.js
CHANGED
|
@@ -129,18 +129,18 @@ function applyAction(a) {
|
|
|
129
129
|
if (a.action === "update" && a.pack) {
|
|
130
130
|
const existing = packs.readPack(a.pack);
|
|
131
131
|
if (!existing) return null;
|
|
132
|
-
packs.updatePack(a.pack, {
|
|
132
|
+
const r = packs.updatePack(a.pack, {
|
|
133
133
|
journal: a.journal || "",
|
|
134
134
|
state: typeof a.state === "string" ? a.state : "",
|
|
135
135
|
stance: typeof a.stance === "string" ? a.stance : "",
|
|
136
136
|
procedure: typeof a.procedure === "string" ? a.procedure : "",
|
|
137
|
-
});
|
|
138
|
-
return { kind: "update", dir: a.pack, name: existing.name, note: a.journal || "state updated" };
|
|
137
|
+
}, "reviewer");
|
|
138
|
+
return { kind: "update", dir: a.pack, name: existing.name, note: a.journal || "state updated", protectedStance: !!r.protectedStance };
|
|
139
139
|
}
|
|
140
140
|
if (a.action === "create" && (a.dir || a.name)) {
|
|
141
141
|
const dir = packs.slugify(a.dir || a.name);
|
|
142
142
|
if (packs.readPack(dir)) {
|
|
143
|
-
packs.updatePack(dir, { journal: a.journal || "", state: a.state || "" });
|
|
143
|
+
packs.updatePack(dir, { journal: a.journal || "", state: a.state || "" }, "reviewer");
|
|
144
144
|
return { kind: "update", dir, name: a.name || dir, note: a.journal || "state updated" };
|
|
145
145
|
}
|
|
146
146
|
const pack = packs.createPack({
|
|
@@ -152,7 +152,7 @@ function applyAction(a) {
|
|
|
152
152
|
procedure: a.procedure || "",
|
|
153
153
|
state: a.state || "",
|
|
154
154
|
journal: a.journal || "",
|
|
155
|
-
});
|
|
155
|
+
}, "reviewer");
|
|
156
156
|
return { kind: "create", dir: pack.dir, name: pack.name, note: a.description || "" };
|
|
157
157
|
}
|
|
158
158
|
return null;
|
package/core/packs.js
CHANGED
|
@@ -137,7 +137,7 @@ function writePack(pack) {
|
|
|
137
137
|
return pack.dir;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
function createPack({ dir, name, description, tags, stance, procedure, state, journal, parent }) {
|
|
140
|
+
function createPack({ dir, name, description, tags, stance, procedure, state, journal, parent }, origin = "user") {
|
|
141
141
|
const d = slugify(dir || name);
|
|
142
142
|
if (!d) throw new Error("pack needs a name");
|
|
143
143
|
if (readPack(d)) throw new Error(`pack ${d} already exists`);
|
|
@@ -156,6 +156,7 @@ function createPack({ dir, name, description, tags, stance, procedure, state, jo
|
|
|
156
156
|
},
|
|
157
157
|
};
|
|
158
158
|
writePack(pack);
|
|
159
|
+
setProvenance(d, SECTIONS.filter((s) => (pack.sections[s] || "").trim()), origin);
|
|
159
160
|
return pack;
|
|
160
161
|
}
|
|
161
162
|
|
|
@@ -163,23 +164,98 @@ function today() {
|
|
|
163
164
|
return new Date().toISOString().slice(0, 10);
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Provenance: a sidecar .provenance.json per pack records which origin
|
|
169
|
+
// (user | reviewer | dream) last authored each section. Sections we never
|
|
170
|
+
// explicitly stamped default to "user" — anything not proven to be automated
|
|
171
|
+
// is protected as if you wrote it. This is what lets the reviewer and dream
|
|
172
|
+
// rewrite their own output freely while never silently clobbering your words.
|
|
173
|
+
function provFile(dir) {
|
|
174
|
+
return path.join(PACKS_DIR, dir, ".provenance.json");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function readProvenance(dir) {
|
|
178
|
+
let data = {};
|
|
179
|
+
try { data = JSON.parse(fs.readFileSync(provFile(dir), "utf-8")) || {}; } catch (e) {}
|
|
180
|
+
const sections = data.sections && typeof data.sections === "object" ? data.sections : {};
|
|
181
|
+
return { sections, lastWriter: data.lastWriter || "", ts: data.ts || "" };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function provenanceOf(dir, section) {
|
|
185
|
+
return readProvenance(dir).sections[section] || "user";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function setProvenance(dir, sectionNames, origin) {
|
|
189
|
+
if (!origin) return;
|
|
190
|
+
const names = [].concat(sectionNames || []).filter(Boolean);
|
|
191
|
+
if (!names.length) return;
|
|
192
|
+
const cur = readProvenance(dir);
|
|
193
|
+
for (const n of names) cur.sections[n] = origin;
|
|
194
|
+
cur.lastWriter = origin;
|
|
195
|
+
cur.ts = new Date().toISOString();
|
|
196
|
+
try {
|
|
197
|
+
fs.mkdirSync(path.join(PACKS_DIR, dir), { recursive: true, mode: 0o700 });
|
|
198
|
+
fs.writeFileSync(provFile(dir), JSON.stringify(cur, null, 2), { mode: 0o600 });
|
|
199
|
+
} catch (e) {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// A foreground Write/Edit to a PACK.md is you (or me acting on what you told
|
|
203
|
+
// me), so stamp the touched section(s) "user" — this also resets a section the
|
|
204
|
+
// reviewer/dream previously claimed back to user-authored once you edit it.
|
|
205
|
+
function recordForegroundWrite(dir, { tool, oldString, content } = {}) {
|
|
206
|
+
try {
|
|
207
|
+
if (tool === "Write" && typeof content === "string") {
|
|
208
|
+
const { body } = parseFrontmatter(content);
|
|
209
|
+
const { sections } = parseSections(body);
|
|
210
|
+
const applied = SECTIONS.filter((s) => sections[s] && sections[s].trim());
|
|
211
|
+
setProvenance(dir, applied.length ? applied : SECTIONS, "user");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (tool === "Edit" && typeof oldString === "string" && oldString) {
|
|
215
|
+
const cur = fs.readFileSync(packFile(dir), "utf-8");
|
|
216
|
+
const idx = cur.indexOf(oldString);
|
|
217
|
+
if (idx < 0) { setProvenance(dir, SECTIONS, "user"); return; }
|
|
218
|
+
let found = null;
|
|
219
|
+
for (const s of SECTIONS) {
|
|
220
|
+
const h = cur.indexOf(`## ${s}`);
|
|
221
|
+
if (h >= 0 && h <= idx && (found === null || h > found.pos)) found = { sec: s, pos: h };
|
|
222
|
+
}
|
|
223
|
+
setProvenance(dir, found ? [found.sec] : SECTIONS, "user");
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
166
228
|
// Apply a reviewer mutation. Only supplied fields change; journal entries
|
|
167
229
|
// append (capped); state replaces (it represents "current truth").
|
|
168
|
-
function updatePack(dir, { description, tags, stance, procedure, state, journal } = {}) {
|
|
230
|
+
function updatePack(dir, { description, tags, stance, procedure, state, journal } = {}, origin = "user") {
|
|
169
231
|
const pack = readPack(dir);
|
|
170
232
|
if (!pack) throw new Error(`no pack: ${dir}`);
|
|
171
233
|
pack.updated = new Date().toISOString();
|
|
172
234
|
if (description) pack.description = description;
|
|
173
235
|
if (Array.isArray(tags) && tags.length) pack.tags = tags;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (typeof
|
|
236
|
+
const applied = [];
|
|
237
|
+
let protectedStance = false;
|
|
238
|
+
if (typeof stance === "string" && stance.trim()) {
|
|
239
|
+
// Stance shapes behaviour, so a user-authored Stance is never silently
|
|
240
|
+
// overwritten by an automated writer — your intent wins.
|
|
241
|
+
if (origin !== "user" && provenanceOf(dir, "Stance") === "user") {
|
|
242
|
+
protectedStance = true;
|
|
243
|
+
} else {
|
|
244
|
+
pack.sections.Stance = stance.trim();
|
|
245
|
+
applied.push("Stance");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (typeof procedure === "string" && procedure.trim()) { pack.sections.Procedure = procedure.trim(); applied.push("Procedure"); }
|
|
249
|
+
if (typeof state === "string" && state.trim()) { pack.sections.State = state.trim(); applied.push("State"); }
|
|
177
250
|
if (typeof journal === "string" && journal.trim()) {
|
|
178
251
|
const entries = pack.sections.Journal.split("\n").filter((l) => l.trim());
|
|
179
252
|
entries.push(`- [${today()}] ${journal.trim().replace(/\n+/g, " ")}`);
|
|
180
253
|
pack.sections.Journal = entries.slice(-MAX_JOURNAL_ENTRIES).join("\n");
|
|
254
|
+
applied.push("Journal");
|
|
181
255
|
}
|
|
182
256
|
writePack(pack);
|
|
257
|
+
if (applied.length) setProvenance(dir, applied, origin);
|
|
258
|
+
pack.protectedStance = protectedStance;
|
|
183
259
|
return pack;
|
|
184
260
|
}
|
|
185
261
|
|
|
@@ -396,4 +472,5 @@ module.exports = {
|
|
|
396
472
|
listPacks, findPack, readPack, writePack, createPack, updatePack, removePack,
|
|
397
473
|
touchUsed, packNameFromPath, matchPacks, reindex, markIndexDirty,
|
|
398
474
|
archivePack, restorePack, listArchived,
|
|
475
|
+
readProvenance, provenanceOf, setProvenance, recordForegroundWrite,
|
|
399
476
|
};
|
package/core/runner.js
CHANGED
|
@@ -806,6 +806,13 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
806
806
|
if (packDir) {
|
|
807
807
|
if (packsLib.readPack(packDir)) notifySkill(`pack:${packDir}`, `✏️ Updating my notes on ${packDir}…`);
|
|
808
808
|
else notifySkill(`pack:${packDir}`, `📦 Starting a new pack: ${packDir} — open-claudia pack show ${packDir} to peek.`);
|
|
809
|
+
try {
|
|
810
|
+
packsLib.recordForegroundWrite(packDir, {
|
|
811
|
+
tool: toolName,
|
|
812
|
+
oldString: input?.old_string ?? input?.oldString,
|
|
813
|
+
content: input?.content,
|
|
814
|
+
});
|
|
815
|
+
} catch (e) {}
|
|
809
816
|
return;
|
|
810
817
|
}
|
|
811
818
|
const entSlug = entitiesLib.entityNameFromPath(filePath);
|
package/package.json
CHANGED