@deeplake/hivemind 0.7.18 → 0.7.20
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/codex/bundle/skillify-worker.js +55 -34
- package/codex/bundle/stop.js +28 -2
- package/cursor/bundle/capture.js +28 -2
- package/cursor/bundle/session-end.js +28 -2
- package/cursor/bundle/skillify-worker.js +55 -34
- package/hermes/bundle/capture.js +28 -2
- package/hermes/bundle/session-end.js +28 -2
- package/hermes/bundle/skillify-worker.js +55 -34
- package/openclaw/dist/index.js +1 -1
- package/openclaw/dist/skillify-worker.js +55 -34
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
|
|
9
|
-
"version": "0.7.
|
|
9
|
+
"version": "0.7.20"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "hivemind",
|
|
14
14
|
"description": "Persistent shared memory powered by Deeplake — captures all session activity and provides cross-session, cross-agent memory search",
|
|
15
|
-
"version": "0.7.
|
|
15
|
+
"version": "0.7.20",
|
|
16
16
|
"source": "./claude-code",
|
|
17
17
|
"homepage": "https://github.com/activeloopai/hivemind"
|
|
18
18
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hivemind",
|
|
3
3
|
"description": "Cloud-backed persistent memory powered by Deeplake — read, write, and share memory across Claude Code sessions and agents",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.20",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Activeloop",
|
|
7
7
|
"url": "https://deeplake.ai"
|
|
@@ -166,7 +166,7 @@ function writeNewSkill(args) {
|
|
|
166
166
|
${args.body.trim()}
|
|
167
167
|
`;
|
|
168
168
|
writeFileSync(path, text);
|
|
169
|
-
return { path, action: "created", version: 1 };
|
|
169
|
+
return { path, action: "created", version: 1, createdAt: now, updatedAt: now };
|
|
170
170
|
}
|
|
171
171
|
function mergeSkill(args) {
|
|
172
172
|
assertValidSkillName(args.name);
|
|
@@ -195,7 +195,7 @@ function mergeSkill(args) {
|
|
|
195
195
|
${args.body.trim()}
|
|
196
196
|
`;
|
|
197
197
|
writeFileSync(path, text);
|
|
198
|
-
return { path, action: "merged", version: fm.version };
|
|
198
|
+
return { path, action: "merged", version: fm.version, createdAt: fm.created_at, updatedAt: fm.updated_at };
|
|
199
199
|
}
|
|
200
200
|
function listSkills(skillsRoot) {
|
|
201
201
|
if (!existsSync(skillsRoot))
|
|
@@ -216,6 +216,50 @@ function resolveSkillsRoot(install, cwd) {
|
|
|
216
216
|
return join2(cwd, ".claude", "skills");
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// dist/src/skillify/existing-skills.js
|
|
220
|
+
function listAllExistingSkills(cwd) {
|
|
221
|
+
const projectRoot = resolveSkillsRoot("project", cwd);
|
|
222
|
+
const globalRoot = resolveSkillsRoot("global", cwd);
|
|
223
|
+
const tagged = [
|
|
224
|
+
...listSkills(projectRoot).map((s) => ({ ...s, source: "project" })),
|
|
225
|
+
...listSkills(globalRoot).map((s) => ({ ...s, source: "global" }))
|
|
226
|
+
];
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
const out = [];
|
|
229
|
+
for (const s of tagged) {
|
|
230
|
+
if (seen.has(s.name))
|
|
231
|
+
continue;
|
|
232
|
+
seen.add(s.name);
|
|
233
|
+
out.push(s);
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
function renderExistingSkillsBlock(cwd, charCap) {
|
|
238
|
+
const skills = listAllExistingSkills(cwd);
|
|
239
|
+
if (skills.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
mergeTargetNames: [],
|
|
242
|
+
block: "(no existing skills \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const mergeTargetNames = skills.filter((s) => s.source === "project").map((s) => s.name);
|
|
246
|
+
let total = 0;
|
|
247
|
+
const out = [];
|
|
248
|
+
for (const s of skills) {
|
|
249
|
+
const tag = s.source === "project" ? "[project]" : "[global, read-only]";
|
|
250
|
+
const block = `--- existing skill ${tag}: ${s.name} ---
|
|
251
|
+
${s.body}
|
|
252
|
+
`;
|
|
253
|
+
if (total + block.length > charCap) {
|
|
254
|
+
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
out.push(block);
|
|
258
|
+
total += block.length;
|
|
259
|
+
}
|
|
260
|
+
return { mergeTargetNames, block: out.join("\n") };
|
|
261
|
+
}
|
|
262
|
+
|
|
219
263
|
// dist/src/skillify/skills-table.js
|
|
220
264
|
import { randomUUID } from "node:crypto";
|
|
221
265
|
|
|
@@ -675,34 +719,9 @@ ${answer}
|
|
|
675
719
|
}
|
|
676
720
|
return out.join("\n");
|
|
677
721
|
}
|
|
678
|
-
function renderExistingSkillsBlock() {
|
|
679
|
-
const skills = listSkills(resolveSkillsRoot(cfg.install, cfg.cwd));
|
|
680
|
-
if (skills.length === 0) {
|
|
681
|
-
return {
|
|
682
|
-
names: [],
|
|
683
|
-
block: "(no existing skills in this project \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
let total = 0;
|
|
687
|
-
const out = [];
|
|
688
|
-
const names = [];
|
|
689
|
-
for (const s of skills) {
|
|
690
|
-
const block = `--- existing skill: ${s.name} ---
|
|
691
|
-
${s.body}
|
|
692
|
-
`;
|
|
693
|
-
if (total + block.length > EXISTING_SKILLS_CHAR_CAP) {
|
|
694
|
-
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
697
|
-
out.push(block);
|
|
698
|
-
names.push(s.name);
|
|
699
|
-
total += block.length;
|
|
700
|
-
}
|
|
701
|
-
return { names, block: out.join("\n") };
|
|
702
|
-
}
|
|
703
722
|
function buildPrompt(pairs) {
|
|
704
|
-
const existing = renderExistingSkillsBlock();
|
|
705
|
-
const mergeTargetsClause = existing.
|
|
723
|
+
const existing = renderExistingSkillsBlock(cfg.cwd, EXISTING_SKILLS_CHAR_CAP);
|
|
724
|
+
const mergeTargetsClause = existing.mergeTargetNames.length > 0 ? `MERGE is allowed only if your "name" is EXACTLY one of: [${existing.mergeTargetNames.join(", ")}]. Any other name MUST use KEEP, not MERGE.` : `MERGE is FORBIDDEN \u2014 there are no project skills to merge into. Use KEEP or SKIP only.`;
|
|
706
725
|
return [
|
|
707
726
|
`You are a skill curator for the "${cfg.project}" project. You decide whether the recent`,
|
|
708
727
|
`agent activity below contains a recurring, non-trivial pattern worth crystallizing as a`,
|
|
@@ -716,12 +735,14 @@ function buildPrompt(pairs) {
|
|
|
716
735
|
` merged body that incorporates the new evidence without exceeding ~3000 characters or`,
|
|
717
736
|
` covering unrelated domains.`,
|
|
718
737
|
`- ${mergeTargetsClause}`,
|
|
719
|
-
`-
|
|
720
|
-
`
|
|
738
|
+
`- Skills tagged [global, read-only] are autopulled from the team's shared skills`,
|
|
739
|
+
` table. They exist so you can recognise patterns already covered globally and pick`,
|
|
740
|
+
` SKIP (or a more specific KEEP) instead of duplicating them. They are NOT valid`,
|
|
741
|
+
` MERGE targets \u2014 only [project] skills can be merged into.`,
|
|
721
742
|
`- Skill bodies should follow the existing style: short sections (When to use, Workflow,`,
|
|
722
743
|
` Anti-patterns), concrete commands and file paths drawn from the exchanges, no marketing.`,
|
|
723
744
|
``,
|
|
724
|
-
`=== EXISTING
|
|
745
|
+
`=== EXISTING SKILLS ([project] are MERGE-eligible, [global] are reference only) ===`,
|
|
725
746
|
existing.block,
|
|
726
747
|
``,
|
|
727
748
|
`=== RECENT EXCHANGES (prompt + answer pairs, tool calls already stripped) ===`,
|
|
@@ -861,8 +882,8 @@ async function main() {
|
|
|
861
882
|
trigger: verdict2.trigger,
|
|
862
883
|
body: verdict2.body,
|
|
863
884
|
version: result.version,
|
|
864
|
-
createdAt:
|
|
865
|
-
updatedAt:
|
|
885
|
+
createdAt: result.createdAt,
|
|
886
|
+
updatedAt: result.updatedAt
|
|
866
887
|
});
|
|
867
888
|
wlog(`recorded to skills table: name=${verdict2.name} v${result.version}`);
|
|
868
889
|
} catch (e) {
|
package/codex/bundle/stop.js
CHANGED
|
@@ -823,15 +823,41 @@ function statePath(projectKey) {
|
|
|
823
823
|
function lockPath(projectKey) {
|
|
824
824
|
return join9(STATE_DIR, `${projectKey}.lock`);
|
|
825
825
|
}
|
|
826
|
+
var DEFAULT_PORTS = {
|
|
827
|
+
http: "80",
|
|
828
|
+
https: "443",
|
|
829
|
+
ssh: "22",
|
|
830
|
+
git: "9418"
|
|
831
|
+
};
|
|
832
|
+
function normalizeGitRemoteUrl(url) {
|
|
833
|
+
let s = url.trim();
|
|
834
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
835
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
836
|
+
if (schemeMatch)
|
|
837
|
+
s = s.slice(schemeMatch[0].length);
|
|
838
|
+
if (!scheme) {
|
|
839
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
840
|
+
if (scp)
|
|
841
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
842
|
+
}
|
|
843
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
844
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
845
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
846
|
+
}
|
|
847
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
848
|
+
s = s.replace(/\/+$/, "");
|
|
849
|
+
return s.toLowerCase();
|
|
850
|
+
}
|
|
826
851
|
function deriveProjectKey(cwd) {
|
|
827
852
|
const project = basename(cwd) || "unknown";
|
|
828
853
|
let signature = null;
|
|
829
854
|
try {
|
|
830
|
-
|
|
855
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
831
856
|
cwd,
|
|
832
857
|
encoding: "utf-8",
|
|
833
858
|
stdio: ["ignore", "pipe", "ignore"]
|
|
834
|
-
}).trim()
|
|
859
|
+
}).trim();
|
|
860
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
835
861
|
} catch {
|
|
836
862
|
}
|
|
837
863
|
const input = signature ?? cwd;
|
package/cursor/bundle/capture.js
CHANGED
|
@@ -1240,15 +1240,41 @@ function statePath2(projectKey) {
|
|
|
1240
1240
|
function lockPath2(projectKey) {
|
|
1241
1241
|
return join12(STATE_DIR2, `${projectKey}.lock`);
|
|
1242
1242
|
}
|
|
1243
|
+
var DEFAULT_PORTS = {
|
|
1244
|
+
http: "80",
|
|
1245
|
+
https: "443",
|
|
1246
|
+
ssh: "22",
|
|
1247
|
+
git: "9418"
|
|
1248
|
+
};
|
|
1249
|
+
function normalizeGitRemoteUrl(url) {
|
|
1250
|
+
let s = url.trim();
|
|
1251
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
1252
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
1253
|
+
if (schemeMatch)
|
|
1254
|
+
s = s.slice(schemeMatch[0].length);
|
|
1255
|
+
if (!scheme) {
|
|
1256
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
1257
|
+
if (scp)
|
|
1258
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
1259
|
+
}
|
|
1260
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
1261
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
1262
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
1263
|
+
}
|
|
1264
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
1265
|
+
s = s.replace(/\/+$/, "");
|
|
1266
|
+
return s.toLowerCase();
|
|
1267
|
+
}
|
|
1243
1268
|
function deriveProjectKey(cwd) {
|
|
1244
1269
|
const project = basename(cwd) || "unknown";
|
|
1245
1270
|
let signature = null;
|
|
1246
1271
|
try {
|
|
1247
|
-
|
|
1272
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
1248
1273
|
cwd,
|
|
1249
1274
|
encoding: "utf-8",
|
|
1250
1275
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1251
|
-
}).trim()
|
|
1276
|
+
}).trim();
|
|
1277
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
1252
1278
|
} catch {
|
|
1253
1279
|
}
|
|
1254
1280
|
const input = signature ?? cwd;
|
|
@@ -374,15 +374,41 @@ function statePath(projectKey) {
|
|
|
374
374
|
function lockPath2(projectKey) {
|
|
375
375
|
return join9(STATE_DIR2, `${projectKey}.lock`);
|
|
376
376
|
}
|
|
377
|
+
var DEFAULT_PORTS = {
|
|
378
|
+
http: "80",
|
|
379
|
+
https: "443",
|
|
380
|
+
ssh: "22",
|
|
381
|
+
git: "9418"
|
|
382
|
+
};
|
|
383
|
+
function normalizeGitRemoteUrl(url) {
|
|
384
|
+
let s = url.trim();
|
|
385
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
386
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
387
|
+
if (schemeMatch)
|
|
388
|
+
s = s.slice(schemeMatch[0].length);
|
|
389
|
+
if (!scheme) {
|
|
390
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
391
|
+
if (scp)
|
|
392
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
393
|
+
}
|
|
394
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
395
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
396
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
397
|
+
}
|
|
398
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
399
|
+
s = s.replace(/\/+$/, "");
|
|
400
|
+
return s.toLowerCase();
|
|
401
|
+
}
|
|
377
402
|
function deriveProjectKey(cwd) {
|
|
378
403
|
const project = basename(cwd) || "unknown";
|
|
379
404
|
let signature = null;
|
|
380
405
|
try {
|
|
381
|
-
|
|
406
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
382
407
|
cwd,
|
|
383
408
|
encoding: "utf-8",
|
|
384
409
|
stdio: ["ignore", "pipe", "ignore"]
|
|
385
|
-
}).trim()
|
|
410
|
+
}).trim();
|
|
411
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
386
412
|
} catch {
|
|
387
413
|
}
|
|
388
414
|
const input = signature ?? cwd;
|
|
@@ -166,7 +166,7 @@ function writeNewSkill(args) {
|
|
|
166
166
|
${args.body.trim()}
|
|
167
167
|
`;
|
|
168
168
|
writeFileSync(path, text);
|
|
169
|
-
return { path, action: "created", version: 1 };
|
|
169
|
+
return { path, action: "created", version: 1, createdAt: now, updatedAt: now };
|
|
170
170
|
}
|
|
171
171
|
function mergeSkill(args) {
|
|
172
172
|
assertValidSkillName(args.name);
|
|
@@ -195,7 +195,7 @@ function mergeSkill(args) {
|
|
|
195
195
|
${args.body.trim()}
|
|
196
196
|
`;
|
|
197
197
|
writeFileSync(path, text);
|
|
198
|
-
return { path, action: "merged", version: fm.version };
|
|
198
|
+
return { path, action: "merged", version: fm.version, createdAt: fm.created_at, updatedAt: fm.updated_at };
|
|
199
199
|
}
|
|
200
200
|
function listSkills(skillsRoot) {
|
|
201
201
|
if (!existsSync(skillsRoot))
|
|
@@ -216,6 +216,50 @@ function resolveSkillsRoot(install, cwd) {
|
|
|
216
216
|
return join2(cwd, ".claude", "skills");
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// dist/src/skillify/existing-skills.js
|
|
220
|
+
function listAllExistingSkills(cwd) {
|
|
221
|
+
const projectRoot = resolveSkillsRoot("project", cwd);
|
|
222
|
+
const globalRoot = resolveSkillsRoot("global", cwd);
|
|
223
|
+
const tagged = [
|
|
224
|
+
...listSkills(projectRoot).map((s) => ({ ...s, source: "project" })),
|
|
225
|
+
...listSkills(globalRoot).map((s) => ({ ...s, source: "global" }))
|
|
226
|
+
];
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
const out = [];
|
|
229
|
+
for (const s of tagged) {
|
|
230
|
+
if (seen.has(s.name))
|
|
231
|
+
continue;
|
|
232
|
+
seen.add(s.name);
|
|
233
|
+
out.push(s);
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
function renderExistingSkillsBlock(cwd, charCap) {
|
|
238
|
+
const skills = listAllExistingSkills(cwd);
|
|
239
|
+
if (skills.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
mergeTargetNames: [],
|
|
242
|
+
block: "(no existing skills \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const mergeTargetNames = skills.filter((s) => s.source === "project").map((s) => s.name);
|
|
246
|
+
let total = 0;
|
|
247
|
+
const out = [];
|
|
248
|
+
for (const s of skills) {
|
|
249
|
+
const tag = s.source === "project" ? "[project]" : "[global, read-only]";
|
|
250
|
+
const block = `--- existing skill ${tag}: ${s.name} ---
|
|
251
|
+
${s.body}
|
|
252
|
+
`;
|
|
253
|
+
if (total + block.length > charCap) {
|
|
254
|
+
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
out.push(block);
|
|
258
|
+
total += block.length;
|
|
259
|
+
}
|
|
260
|
+
return { mergeTargetNames, block: out.join("\n") };
|
|
261
|
+
}
|
|
262
|
+
|
|
219
263
|
// dist/src/skillify/skills-table.js
|
|
220
264
|
import { randomUUID } from "node:crypto";
|
|
221
265
|
|
|
@@ -675,34 +719,9 @@ ${answer}
|
|
|
675
719
|
}
|
|
676
720
|
return out.join("\n");
|
|
677
721
|
}
|
|
678
|
-
function renderExistingSkillsBlock() {
|
|
679
|
-
const skills = listSkills(resolveSkillsRoot(cfg.install, cfg.cwd));
|
|
680
|
-
if (skills.length === 0) {
|
|
681
|
-
return {
|
|
682
|
-
names: [],
|
|
683
|
-
block: "(no existing skills in this project \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
let total = 0;
|
|
687
|
-
const out = [];
|
|
688
|
-
const names = [];
|
|
689
|
-
for (const s of skills) {
|
|
690
|
-
const block = `--- existing skill: ${s.name} ---
|
|
691
|
-
${s.body}
|
|
692
|
-
`;
|
|
693
|
-
if (total + block.length > EXISTING_SKILLS_CHAR_CAP) {
|
|
694
|
-
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
697
|
-
out.push(block);
|
|
698
|
-
names.push(s.name);
|
|
699
|
-
total += block.length;
|
|
700
|
-
}
|
|
701
|
-
return { names, block: out.join("\n") };
|
|
702
|
-
}
|
|
703
722
|
function buildPrompt(pairs) {
|
|
704
|
-
const existing = renderExistingSkillsBlock();
|
|
705
|
-
const mergeTargetsClause = existing.
|
|
723
|
+
const existing = renderExistingSkillsBlock(cfg.cwd, EXISTING_SKILLS_CHAR_CAP);
|
|
724
|
+
const mergeTargetsClause = existing.mergeTargetNames.length > 0 ? `MERGE is allowed only if your "name" is EXACTLY one of: [${existing.mergeTargetNames.join(", ")}]. Any other name MUST use KEEP, not MERGE.` : `MERGE is FORBIDDEN \u2014 there are no project skills to merge into. Use KEEP or SKIP only.`;
|
|
706
725
|
return [
|
|
707
726
|
`You are a skill curator for the "${cfg.project}" project. You decide whether the recent`,
|
|
708
727
|
`agent activity below contains a recurring, non-trivial pattern worth crystallizing as a`,
|
|
@@ -716,12 +735,14 @@ function buildPrompt(pairs) {
|
|
|
716
735
|
` merged body that incorporates the new evidence without exceeding ~3000 characters or`,
|
|
717
736
|
` covering unrelated domains.`,
|
|
718
737
|
`- ${mergeTargetsClause}`,
|
|
719
|
-
`-
|
|
720
|
-
`
|
|
738
|
+
`- Skills tagged [global, read-only] are autopulled from the team's shared skills`,
|
|
739
|
+
` table. They exist so you can recognise patterns already covered globally and pick`,
|
|
740
|
+
` SKIP (or a more specific KEEP) instead of duplicating them. They are NOT valid`,
|
|
741
|
+
` MERGE targets \u2014 only [project] skills can be merged into.`,
|
|
721
742
|
`- Skill bodies should follow the existing style: short sections (When to use, Workflow,`,
|
|
722
743
|
` Anti-patterns), concrete commands and file paths drawn from the exchanges, no marketing.`,
|
|
723
744
|
``,
|
|
724
|
-
`=== EXISTING
|
|
745
|
+
`=== EXISTING SKILLS ([project] are MERGE-eligible, [global] are reference only) ===`,
|
|
725
746
|
existing.block,
|
|
726
747
|
``,
|
|
727
748
|
`=== RECENT EXCHANGES (prompt + answer pairs, tool calls already stripped) ===`,
|
|
@@ -861,8 +882,8 @@ async function main() {
|
|
|
861
882
|
trigger: verdict2.trigger,
|
|
862
883
|
body: verdict2.body,
|
|
863
884
|
version: result.version,
|
|
864
|
-
createdAt:
|
|
865
|
-
updatedAt:
|
|
885
|
+
createdAt: result.createdAt,
|
|
886
|
+
updatedAt: result.updatedAt
|
|
866
887
|
});
|
|
867
888
|
wlog(`recorded to skills table: name=${verdict2.name} v${result.version}`);
|
|
868
889
|
} catch (e) {
|
package/hermes/bundle/capture.js
CHANGED
|
@@ -1240,15 +1240,41 @@ function statePath2(projectKey) {
|
|
|
1240
1240
|
function lockPath2(projectKey) {
|
|
1241
1241
|
return join12(STATE_DIR2, `${projectKey}.lock`);
|
|
1242
1242
|
}
|
|
1243
|
+
var DEFAULT_PORTS = {
|
|
1244
|
+
http: "80",
|
|
1245
|
+
https: "443",
|
|
1246
|
+
ssh: "22",
|
|
1247
|
+
git: "9418"
|
|
1248
|
+
};
|
|
1249
|
+
function normalizeGitRemoteUrl(url) {
|
|
1250
|
+
let s = url.trim();
|
|
1251
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
1252
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
1253
|
+
if (schemeMatch)
|
|
1254
|
+
s = s.slice(schemeMatch[0].length);
|
|
1255
|
+
if (!scheme) {
|
|
1256
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
1257
|
+
if (scp)
|
|
1258
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
1259
|
+
}
|
|
1260
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
1261
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
1262
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
1263
|
+
}
|
|
1264
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
1265
|
+
s = s.replace(/\/+$/, "");
|
|
1266
|
+
return s.toLowerCase();
|
|
1267
|
+
}
|
|
1243
1268
|
function deriveProjectKey(cwd) {
|
|
1244
1269
|
const project = basename(cwd) || "unknown";
|
|
1245
1270
|
let signature = null;
|
|
1246
1271
|
try {
|
|
1247
|
-
|
|
1272
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
1248
1273
|
cwd,
|
|
1249
1274
|
encoding: "utf-8",
|
|
1250
1275
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1251
|
-
}).trim()
|
|
1276
|
+
}).trim();
|
|
1277
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
1252
1278
|
} catch {
|
|
1253
1279
|
}
|
|
1254
1280
|
const input = signature ?? cwd;
|
|
@@ -373,15 +373,41 @@ function statePath(projectKey) {
|
|
|
373
373
|
function lockPath2(projectKey) {
|
|
374
374
|
return join9(STATE_DIR2, `${projectKey}.lock`);
|
|
375
375
|
}
|
|
376
|
+
var DEFAULT_PORTS = {
|
|
377
|
+
http: "80",
|
|
378
|
+
https: "443",
|
|
379
|
+
ssh: "22",
|
|
380
|
+
git: "9418"
|
|
381
|
+
};
|
|
382
|
+
function normalizeGitRemoteUrl(url) {
|
|
383
|
+
let s = url.trim();
|
|
384
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
385
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
386
|
+
if (schemeMatch)
|
|
387
|
+
s = s.slice(schemeMatch[0].length);
|
|
388
|
+
if (!scheme) {
|
|
389
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
390
|
+
if (scp)
|
|
391
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
392
|
+
}
|
|
393
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
394
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
395
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
396
|
+
}
|
|
397
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
398
|
+
s = s.replace(/\/+$/, "");
|
|
399
|
+
return s.toLowerCase();
|
|
400
|
+
}
|
|
376
401
|
function deriveProjectKey(cwd) {
|
|
377
402
|
const project = basename(cwd) || "unknown";
|
|
378
403
|
let signature = null;
|
|
379
404
|
try {
|
|
380
|
-
|
|
405
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
381
406
|
cwd,
|
|
382
407
|
encoding: "utf-8",
|
|
383
408
|
stdio: ["ignore", "pipe", "ignore"]
|
|
384
|
-
}).trim()
|
|
409
|
+
}).trim();
|
|
410
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
385
411
|
} catch {
|
|
386
412
|
}
|
|
387
413
|
const input = signature ?? cwd;
|
|
@@ -166,7 +166,7 @@ function writeNewSkill(args) {
|
|
|
166
166
|
${args.body.trim()}
|
|
167
167
|
`;
|
|
168
168
|
writeFileSync(path, text);
|
|
169
|
-
return { path, action: "created", version: 1 };
|
|
169
|
+
return { path, action: "created", version: 1, createdAt: now, updatedAt: now };
|
|
170
170
|
}
|
|
171
171
|
function mergeSkill(args) {
|
|
172
172
|
assertValidSkillName(args.name);
|
|
@@ -195,7 +195,7 @@ function mergeSkill(args) {
|
|
|
195
195
|
${args.body.trim()}
|
|
196
196
|
`;
|
|
197
197
|
writeFileSync(path, text);
|
|
198
|
-
return { path, action: "merged", version: fm.version };
|
|
198
|
+
return { path, action: "merged", version: fm.version, createdAt: fm.created_at, updatedAt: fm.updated_at };
|
|
199
199
|
}
|
|
200
200
|
function listSkills(skillsRoot) {
|
|
201
201
|
if (!existsSync(skillsRoot))
|
|
@@ -216,6 +216,50 @@ function resolveSkillsRoot(install, cwd) {
|
|
|
216
216
|
return join2(cwd, ".claude", "skills");
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// dist/src/skillify/existing-skills.js
|
|
220
|
+
function listAllExistingSkills(cwd) {
|
|
221
|
+
const projectRoot = resolveSkillsRoot("project", cwd);
|
|
222
|
+
const globalRoot = resolveSkillsRoot("global", cwd);
|
|
223
|
+
const tagged = [
|
|
224
|
+
...listSkills(projectRoot).map((s) => ({ ...s, source: "project" })),
|
|
225
|
+
...listSkills(globalRoot).map((s) => ({ ...s, source: "global" }))
|
|
226
|
+
];
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
const out = [];
|
|
229
|
+
for (const s of tagged) {
|
|
230
|
+
if (seen.has(s.name))
|
|
231
|
+
continue;
|
|
232
|
+
seen.add(s.name);
|
|
233
|
+
out.push(s);
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
function renderExistingSkillsBlock(cwd, charCap) {
|
|
238
|
+
const skills = listAllExistingSkills(cwd);
|
|
239
|
+
if (skills.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
mergeTargetNames: [],
|
|
242
|
+
block: "(no existing skills \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const mergeTargetNames = skills.filter((s) => s.source === "project").map((s) => s.name);
|
|
246
|
+
let total = 0;
|
|
247
|
+
const out = [];
|
|
248
|
+
for (const s of skills) {
|
|
249
|
+
const tag = s.source === "project" ? "[project]" : "[global, read-only]";
|
|
250
|
+
const block = `--- existing skill ${tag}: ${s.name} ---
|
|
251
|
+
${s.body}
|
|
252
|
+
`;
|
|
253
|
+
if (total + block.length > charCap) {
|
|
254
|
+
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
out.push(block);
|
|
258
|
+
total += block.length;
|
|
259
|
+
}
|
|
260
|
+
return { mergeTargetNames, block: out.join("\n") };
|
|
261
|
+
}
|
|
262
|
+
|
|
219
263
|
// dist/src/skillify/skills-table.js
|
|
220
264
|
import { randomUUID } from "node:crypto";
|
|
221
265
|
|
|
@@ -675,34 +719,9 @@ ${answer}
|
|
|
675
719
|
}
|
|
676
720
|
return out.join("\n");
|
|
677
721
|
}
|
|
678
|
-
function renderExistingSkillsBlock() {
|
|
679
|
-
const skills = listSkills(resolveSkillsRoot(cfg.install, cfg.cwd));
|
|
680
|
-
if (skills.length === 0) {
|
|
681
|
-
return {
|
|
682
|
-
names: [],
|
|
683
|
-
block: "(no existing skills in this project \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
let total = 0;
|
|
687
|
-
const out = [];
|
|
688
|
-
const names = [];
|
|
689
|
-
for (const s of skills) {
|
|
690
|
-
const block = `--- existing skill: ${s.name} ---
|
|
691
|
-
${s.body}
|
|
692
|
-
`;
|
|
693
|
-
if (total + block.length > EXISTING_SKILLS_CHAR_CAP) {
|
|
694
|
-
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
697
|
-
out.push(block);
|
|
698
|
-
names.push(s.name);
|
|
699
|
-
total += block.length;
|
|
700
|
-
}
|
|
701
|
-
return { names, block: out.join("\n") };
|
|
702
|
-
}
|
|
703
722
|
function buildPrompt(pairs) {
|
|
704
|
-
const existing = renderExistingSkillsBlock();
|
|
705
|
-
const mergeTargetsClause = existing.
|
|
723
|
+
const existing = renderExistingSkillsBlock(cfg.cwd, EXISTING_SKILLS_CHAR_CAP);
|
|
724
|
+
const mergeTargetsClause = existing.mergeTargetNames.length > 0 ? `MERGE is allowed only if your "name" is EXACTLY one of: [${existing.mergeTargetNames.join(", ")}]. Any other name MUST use KEEP, not MERGE.` : `MERGE is FORBIDDEN \u2014 there are no project skills to merge into. Use KEEP or SKIP only.`;
|
|
706
725
|
return [
|
|
707
726
|
`You are a skill curator for the "${cfg.project}" project. You decide whether the recent`,
|
|
708
727
|
`agent activity below contains a recurring, non-trivial pattern worth crystallizing as a`,
|
|
@@ -716,12 +735,14 @@ function buildPrompt(pairs) {
|
|
|
716
735
|
` merged body that incorporates the new evidence without exceeding ~3000 characters or`,
|
|
717
736
|
` covering unrelated domains.`,
|
|
718
737
|
`- ${mergeTargetsClause}`,
|
|
719
|
-
`-
|
|
720
|
-
`
|
|
738
|
+
`- Skills tagged [global, read-only] are autopulled from the team's shared skills`,
|
|
739
|
+
` table. They exist so you can recognise patterns already covered globally and pick`,
|
|
740
|
+
` SKIP (or a more specific KEEP) instead of duplicating them. They are NOT valid`,
|
|
741
|
+
` MERGE targets \u2014 only [project] skills can be merged into.`,
|
|
721
742
|
`- Skill bodies should follow the existing style: short sections (When to use, Workflow,`,
|
|
722
743
|
` Anti-patterns), concrete commands and file paths drawn from the exchanges, no marketing.`,
|
|
723
744
|
``,
|
|
724
|
-
`=== EXISTING
|
|
745
|
+
`=== EXISTING SKILLS ([project] are MERGE-eligible, [global] are reference only) ===`,
|
|
725
746
|
existing.block,
|
|
726
747
|
``,
|
|
727
748
|
`=== RECENT EXCHANGES (prompt + answer pairs, tool calls already stripped) ===`,
|
|
@@ -861,8 +882,8 @@ async function main() {
|
|
|
861
882
|
trigger: verdict2.trigger,
|
|
862
883
|
body: verdict2.body,
|
|
863
884
|
version: result.version,
|
|
864
|
-
createdAt:
|
|
865
|
-
updatedAt:
|
|
885
|
+
createdAt: result.createdAt,
|
|
886
|
+
updatedAt: result.updatedAt
|
|
866
887
|
});
|
|
867
888
|
wlog(`recorded to skills table: name=${verdict2.name} v${result.version}`);
|
|
868
889
|
} catch (e) {
|
package/openclaw/dist/index.js
CHANGED
|
@@ -1071,7 +1071,7 @@ function extractLatestVersion(body) {
|
|
|
1071
1071
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1072
1072
|
}
|
|
1073
1073
|
function getInstalledVersion() {
|
|
1074
|
-
return "0.7.
|
|
1074
|
+
return "0.7.20".length > 0 ? "0.7.20" : null;
|
|
1075
1075
|
}
|
|
1076
1076
|
function isNewer(latest, current) {
|
|
1077
1077
|
const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
|
|
@@ -166,7 +166,7 @@ function writeNewSkill(args) {
|
|
|
166
166
|
${args.body.trim()}
|
|
167
167
|
`;
|
|
168
168
|
writeFileSync(path, text);
|
|
169
|
-
return { path, action: "created", version: 1 };
|
|
169
|
+
return { path, action: "created", version: 1, createdAt: now, updatedAt: now };
|
|
170
170
|
}
|
|
171
171
|
function mergeSkill(args) {
|
|
172
172
|
assertValidSkillName(args.name);
|
|
@@ -195,7 +195,7 @@ function mergeSkill(args) {
|
|
|
195
195
|
${args.body.trim()}
|
|
196
196
|
`;
|
|
197
197
|
writeFileSync(path, text);
|
|
198
|
-
return { path, action: "merged", version: fm.version };
|
|
198
|
+
return { path, action: "merged", version: fm.version, createdAt: fm.created_at, updatedAt: fm.updated_at };
|
|
199
199
|
}
|
|
200
200
|
function listSkills(skillsRoot) {
|
|
201
201
|
if (!existsSync(skillsRoot))
|
|
@@ -216,6 +216,50 @@ function resolveSkillsRoot(install, cwd) {
|
|
|
216
216
|
return join2(cwd, ".claude", "skills");
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
// dist/src/skillify/existing-skills.js
|
|
220
|
+
function listAllExistingSkills(cwd) {
|
|
221
|
+
const projectRoot = resolveSkillsRoot("project", cwd);
|
|
222
|
+
const globalRoot = resolveSkillsRoot("global", cwd);
|
|
223
|
+
const tagged = [
|
|
224
|
+
...listSkills(projectRoot).map((s) => ({ ...s, source: "project" })),
|
|
225
|
+
...listSkills(globalRoot).map((s) => ({ ...s, source: "global" }))
|
|
226
|
+
];
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
const out = [];
|
|
229
|
+
for (const s of tagged) {
|
|
230
|
+
if (seen.has(s.name))
|
|
231
|
+
continue;
|
|
232
|
+
seen.add(s.name);
|
|
233
|
+
out.push(s);
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
function renderExistingSkillsBlock(cwd, charCap) {
|
|
238
|
+
const skills = listAllExistingSkills(cwd);
|
|
239
|
+
if (skills.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
mergeTargetNames: [],
|
|
242
|
+
block: "(no existing skills \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const mergeTargetNames = skills.filter((s) => s.source === "project").map((s) => s.name);
|
|
246
|
+
let total = 0;
|
|
247
|
+
const out = [];
|
|
248
|
+
for (const s of skills) {
|
|
249
|
+
const tag = s.source === "project" ? "[project]" : "[global, read-only]";
|
|
250
|
+
const block = `--- existing skill ${tag}: ${s.name} ---
|
|
251
|
+
${s.body}
|
|
252
|
+
`;
|
|
253
|
+
if (total + block.length > charCap) {
|
|
254
|
+
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
out.push(block);
|
|
258
|
+
total += block.length;
|
|
259
|
+
}
|
|
260
|
+
return { mergeTargetNames, block: out.join("\n") };
|
|
261
|
+
}
|
|
262
|
+
|
|
219
263
|
// dist/src/skillify/skills-table.js
|
|
220
264
|
import { randomUUID } from "node:crypto";
|
|
221
265
|
|
|
@@ -675,34 +719,9 @@ ${answer}
|
|
|
675
719
|
}
|
|
676
720
|
return out.join("\n");
|
|
677
721
|
}
|
|
678
|
-
function renderExistingSkillsBlock() {
|
|
679
|
-
const skills = listSkills(resolveSkillsRoot(cfg.install, cfg.cwd));
|
|
680
|
-
if (skills.length === 0) {
|
|
681
|
-
return {
|
|
682
|
-
names: [],
|
|
683
|
-
block: "(no existing skills in this project \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
let total = 0;
|
|
687
|
-
const out = [];
|
|
688
|
-
const names = [];
|
|
689
|
-
for (const s of skills) {
|
|
690
|
-
const block = `--- existing skill: ${s.name} ---
|
|
691
|
-
${s.body}
|
|
692
|
-
`;
|
|
693
|
-
if (total + block.length > EXISTING_SKILLS_CHAR_CAP) {
|
|
694
|
-
out.push(`[\u2026${skills.length - out.length} more existing skills omitted]`);
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
697
|
-
out.push(block);
|
|
698
|
-
names.push(s.name);
|
|
699
|
-
total += block.length;
|
|
700
|
-
}
|
|
701
|
-
return { names, block: out.join("\n") };
|
|
702
|
-
}
|
|
703
722
|
function buildPrompt(pairs) {
|
|
704
|
-
const existing = renderExistingSkillsBlock();
|
|
705
|
-
const mergeTargetsClause = existing.
|
|
723
|
+
const existing = renderExistingSkillsBlock(cfg.cwd, EXISTING_SKILLS_CHAR_CAP);
|
|
724
|
+
const mergeTargetsClause = existing.mergeTargetNames.length > 0 ? `MERGE is allowed only if your "name" is EXACTLY one of: [${existing.mergeTargetNames.join(", ")}]. Any other name MUST use KEEP, not MERGE.` : `MERGE is FORBIDDEN \u2014 there are no project skills to merge into. Use KEEP or SKIP only.`;
|
|
706
725
|
return [
|
|
707
726
|
`You are a skill curator for the "${cfg.project}" project. You decide whether the recent`,
|
|
708
727
|
`agent activity below contains a recurring, non-trivial pattern worth crystallizing as a`,
|
|
@@ -716,12 +735,14 @@ function buildPrompt(pairs) {
|
|
|
716
735
|
` merged body that incorporates the new evidence without exceeding ~3000 characters or`,
|
|
717
736
|
` covering unrelated domains.`,
|
|
718
737
|
`- ${mergeTargetsClause}`,
|
|
719
|
-
`-
|
|
720
|
-
`
|
|
738
|
+
`- Skills tagged [global, read-only] are autopulled from the team's shared skills`,
|
|
739
|
+
` table. They exist so you can recognise patterns already covered globally and pick`,
|
|
740
|
+
` SKIP (or a more specific KEEP) instead of duplicating them. They are NOT valid`,
|
|
741
|
+
` MERGE targets \u2014 only [project] skills can be merged into.`,
|
|
721
742
|
`- Skill bodies should follow the existing style: short sections (When to use, Workflow,`,
|
|
722
743
|
` Anti-patterns), concrete commands and file paths drawn from the exchanges, no marketing.`,
|
|
723
744
|
``,
|
|
724
|
-
`=== EXISTING
|
|
745
|
+
`=== EXISTING SKILLS ([project] are MERGE-eligible, [global] are reference only) ===`,
|
|
725
746
|
existing.block,
|
|
726
747
|
``,
|
|
727
748
|
`=== RECENT EXCHANGES (prompt + answer pairs, tool calls already stripped) ===`,
|
|
@@ -861,8 +882,8 @@ async function main() {
|
|
|
861
882
|
trigger: verdict2.trigger,
|
|
862
883
|
body: verdict2.body,
|
|
863
884
|
version: result.version,
|
|
864
|
-
createdAt:
|
|
865
|
-
updatedAt:
|
|
885
|
+
createdAt: result.createdAt,
|
|
886
|
+
updatedAt: result.updatedAt
|
|
866
887
|
});
|
|
867
888
|
wlog(`recorded to skills table: name=${verdict2.name} v${result.version}`);
|
|
868
889
|
} catch (e) {
|
package/openclaw/package.json
CHANGED