@deeplake/hivemind 0.7.22 → 0.7.23
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/README.md +1 -1
- package/bundle/cli.js +62 -13
- package/codex/bundle/session-start.js +58 -9
- package/codex/bundle/skillify-worker.js +118 -29
- package/codex/bundle/stop.js +1 -1
- package/cursor/bundle/capture.js +1 -1
- package/cursor/bundle/session-end.js +1 -1
- package/cursor/bundle/session-start.js +58 -9
- package/cursor/bundle/skillify-worker.js +118 -29
- package/hermes/bundle/capture.js +1 -1
- package/hermes/bundle/session-end.js +1 -1
- package/hermes/bundle/session-start.js +58 -9
- package/hermes/bundle/skillify-worker.js +118 -29
- package/openclaw/dist/index.js +3 -3
- package/openclaw/dist/skillify-worker.js +118 -29
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/openclaw/skills/SKILL.md +1 -1
- package/package.json +1 -1
- package/pi/extension-source/hivemind.ts +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.23"
|
|
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.23",
|
|
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.23",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Activeloop",
|
|
7
7
|
"url": "https://deeplake.ai"
|
package/README.md
CHANGED
|
@@ -268,7 +268,7 @@ Hivemind **codifies recurring patterns from your team's recent sessions into reu
|
|
|
268
268
|
|
|
269
269
|
```bash
|
|
270
270
|
hivemind skillify # show current scope, team, install, per-project state
|
|
271
|
-
hivemind skillify scope <me|team
|
|
271
|
+
hivemind skillify scope <me|team> # who counts as "in scope" for mining
|
|
272
272
|
hivemind skillify pull # install teammates' skills locally
|
|
273
273
|
hivemind skillify unpull # remove pulled skills
|
|
274
274
|
```
|
package/bundle/cli.js
CHANGED
|
@@ -4770,7 +4770,7 @@ function loadScopeConfig() {
|
|
|
4770
4770
|
return DEFAULT;
|
|
4771
4771
|
try {
|
|
4772
4772
|
const raw = JSON.parse(readFileSync9(CONFIG_PATH2, "utf-8"));
|
|
4773
|
-
const scope = raw.scope === "team"
|
|
4773
|
+
const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
|
|
4774
4774
|
const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
|
|
4775
4775
|
const install = raw.install === "global" ? "global" : "project";
|
|
4776
4776
|
return { scope, team, install };
|
|
@@ -4816,18 +4816,25 @@ function parseFrontmatter(text) {
|
|
|
4816
4816
|
const head = text.slice(4, end).trim();
|
|
4817
4817
|
const body = text.slice(end + 4).replace(/^\r?\n/, "");
|
|
4818
4818
|
const fm = { source_sessions: [] };
|
|
4819
|
-
let
|
|
4819
|
+
let arrayKey = null;
|
|
4820
4820
|
for (const raw of head.split(/\r?\n/)) {
|
|
4821
|
-
if (
|
|
4821
|
+
if (arrayKey) {
|
|
4822
4822
|
const m2 = raw.match(/^\s+-\s+(.+)$/);
|
|
4823
4823
|
if (m2) {
|
|
4824
|
-
fm
|
|
4824
|
+
const arr = fm[arrayKey] ?? [];
|
|
4825
|
+
arr.push(m2[1].trim());
|
|
4826
|
+
fm[arrayKey] = arr;
|
|
4825
4827
|
continue;
|
|
4826
4828
|
}
|
|
4827
|
-
|
|
4829
|
+
arrayKey = null;
|
|
4828
4830
|
}
|
|
4829
4831
|
if (raw.startsWith("source_sessions:")) {
|
|
4830
|
-
|
|
4832
|
+
arrayKey = "source_sessions";
|
|
4833
|
+
continue;
|
|
4834
|
+
}
|
|
4835
|
+
if (raw.startsWith("contributors:")) {
|
|
4836
|
+
arrayKey = "contributors";
|
|
4837
|
+
fm.contributors = [];
|
|
4831
4838
|
continue;
|
|
4832
4839
|
}
|
|
4833
4840
|
const m = raw.match(/^([a-zA-Z_]+):\s*(.*)$/);
|
|
@@ -5017,11 +5024,19 @@ function buildPullSql(args) {
|
|
|
5017
5024
|
where.push(`name = '${esc(args.skillName)}'`);
|
|
5018
5025
|
}
|
|
5019
5026
|
const whereClause = where.length > 0 ? ` WHERE ${where.join(" AND ")}` : "";
|
|
5020
|
-
|
|
5027
|
+
const contributorsCol = args.includeContributors === false ? "" : "contributors, ";
|
|
5028
|
+
return `SELECT name, project, project_key, body, version, source_agent, scope, author, ${contributorsCol}description, trigger_text, source_sessions, install, created_at, updated_at FROM "${args.tableName}"${whereClause} ORDER BY project_key ASC, name ASC, version DESC`;
|
|
5029
|
+
}
|
|
5030
|
+
function isMissingContributorsColumnError(message) {
|
|
5031
|
+
if (!message)
|
|
5032
|
+
return false;
|
|
5033
|
+
return /contributors.*(?:does not exist|not found|unknown)/i.test(message) || /(?:does not exist|unknown column).*contributors/i.test(message);
|
|
5021
5034
|
}
|
|
5022
5035
|
function isMissingTableError(message) {
|
|
5023
5036
|
if (!message)
|
|
5024
5037
|
return false;
|
|
5038
|
+
if (/\bcolumn\b/i.test(message))
|
|
5039
|
+
return false;
|
|
5025
5040
|
return /Table does not exist|relation .* does not exist|no such table/i.test(message);
|
|
5026
5041
|
}
|
|
5027
5042
|
function resolvePullDestination(install, cwd) {
|
|
@@ -5117,11 +5132,16 @@ function selectLatestPerName(rows) {
|
|
|
5117
5132
|
}
|
|
5118
5133
|
function renderSkillFile(row) {
|
|
5119
5134
|
const sources = parseSourceSessions(row.source_sessions);
|
|
5135
|
+
const author = typeof row.author === "string" && row.author.length > 0 ? row.author : void 0;
|
|
5136
|
+
const contributors = parseContributors(row.contributors);
|
|
5137
|
+
const renderedContributors = contributors.length > 0 ? contributors : author ? [author] : [];
|
|
5120
5138
|
const fm = {
|
|
5121
5139
|
name: String(row.name ?? ""),
|
|
5122
5140
|
description: String(row.description ?? ""),
|
|
5123
5141
|
trigger: typeof row.trigger_text === "string" && row.trigger_text.length > 0 ? String(row.trigger_text) : void 0,
|
|
5142
|
+
author,
|
|
5124
5143
|
source_sessions: sources,
|
|
5144
|
+
contributors: renderedContributors,
|
|
5125
5145
|
version: Number(row.version ?? 1),
|
|
5126
5146
|
created_by_agent: String(row.source_agent ?? "unknown"),
|
|
5127
5147
|
created_at: String(row.created_at ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
@@ -5146,15 +5166,35 @@ function parseSourceSessions(v) {
|
|
|
5146
5166
|
}
|
|
5147
5167
|
return [];
|
|
5148
5168
|
}
|
|
5169
|
+
function parseContributors(v) {
|
|
5170
|
+
if (Array.isArray(v))
|
|
5171
|
+
return v.map(String);
|
|
5172
|
+
if (typeof v === "string") {
|
|
5173
|
+
try {
|
|
5174
|
+
const parsed = JSON.parse(v);
|
|
5175
|
+
if (Array.isArray(parsed))
|
|
5176
|
+
return parsed.map(String);
|
|
5177
|
+
} catch {
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
return [];
|
|
5181
|
+
}
|
|
5149
5182
|
function renderFrontmatter(fm) {
|
|
5150
5183
|
const lines = ["---"];
|
|
5151
5184
|
lines.push(`name: ${fm.name}`);
|
|
5152
5185
|
lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
5153
5186
|
if (fm.trigger)
|
|
5154
5187
|
lines.push(`trigger: ${JSON.stringify(fm.trigger)}`);
|
|
5188
|
+
if (fm.author)
|
|
5189
|
+
lines.push(`author: ${fm.author}`);
|
|
5155
5190
|
lines.push(`source_sessions:`);
|
|
5156
5191
|
for (const s of fm.source_sessions)
|
|
5157
5192
|
lines.push(` - ${s}`);
|
|
5193
|
+
if (fm.contributors && fm.contributors.length > 0) {
|
|
5194
|
+
lines.push(`contributors:`);
|
|
5195
|
+
for (const c of fm.contributors)
|
|
5196
|
+
lines.push(` - ${c}`);
|
|
5197
|
+
}
|
|
5158
5198
|
lines.push(`version: ${fm.version}`);
|
|
5159
5199
|
lines.push(`created_by_agent: ${fm.created_by_agent}`);
|
|
5160
5200
|
lines.push(`created_at: ${fm.created_at}`);
|
|
@@ -5194,10 +5234,19 @@ async function runPull(opts) {
|
|
|
5194
5234
|
try {
|
|
5195
5235
|
rows = await opts.query(sql);
|
|
5196
5236
|
} catch (e) {
|
|
5197
|
-
if (isMissingTableError(e?.message))
|
|
5237
|
+
if (isMissingTableError(e?.message)) {
|
|
5198
5238
|
rows = [];
|
|
5199
|
-
else
|
|
5239
|
+
} else if (isMissingContributorsColumnError(e?.message)) {
|
|
5240
|
+
const legacySql = buildPullSql({
|
|
5241
|
+
tableName: opts.tableName,
|
|
5242
|
+
users: opts.users,
|
|
5243
|
+
skillName: opts.skillName,
|
|
5244
|
+
includeContributors: false
|
|
5245
|
+
});
|
|
5246
|
+
rows = await opts.query(legacySql);
|
|
5247
|
+
} else {
|
|
5200
5248
|
throw e;
|
|
5249
|
+
}
|
|
5201
5250
|
}
|
|
5202
5251
|
const latest = selectLatestPerName(rows);
|
|
5203
5252
|
const root = resolvePullDestination(opts.install, opts.cwd);
|
|
@@ -5511,8 +5560,8 @@ function showStatus() {
|
|
|
5511
5560
|
}
|
|
5512
5561
|
}
|
|
5513
5562
|
function setScope(scope) {
|
|
5514
|
-
if (scope !== "me" && scope !== "team"
|
|
5515
|
-
console.error(`Invalid scope '${scope}'. Use one of: me, team
|
|
5563
|
+
if (scope !== "me" && scope !== "team") {
|
|
5564
|
+
console.error(`Invalid scope '${scope}'. Use one of: me, team`);
|
|
5516
5565
|
process.exit(1);
|
|
5517
5566
|
}
|
|
5518
5567
|
const cfg = loadScopeConfig();
|
|
@@ -5591,7 +5640,7 @@ function teamList() {
|
|
|
5591
5640
|
function usage() {
|
|
5592
5641
|
console.log("Usage:");
|
|
5593
5642
|
console.log(" hivemind skillify show current scope, team, install, and per-project state");
|
|
5594
|
-
console.log(" hivemind skillify scope <me|team
|
|
5643
|
+
console.log(" hivemind skillify scope <me|team> set the mining scope");
|
|
5595
5644
|
console.log(" hivemind skillify install <project|global> set where new skills are written");
|
|
5596
5645
|
console.log(" hivemind skillify promote <skill-name> move a project skill to the global location");
|
|
5597
5646
|
console.log(" hivemind skillify team add <username> add a username to the team list");
|
|
@@ -6041,7 +6090,7 @@ Skill management (mine + share reusable Claude skills across the org):
|
|
|
6041
6090
|
--to <project|global>, --dry-run,
|
|
6042
6091
|
--all (also locally-mined),
|
|
6043
6092
|
--legacy-cleanup (pre-suffix-author dirs).
|
|
6044
|
-
hivemind skillify scope <me|team
|
|
6093
|
+
hivemind skillify scope <me|team> Set the sharing scope for newly mined skills.
|
|
6045
6094
|
hivemind skillify install <project|global> Set where new skills are written.
|
|
6046
6095
|
hivemind skillify promote <name> Move a project skill to the global location.
|
|
6047
6096
|
hivemind skillify team add <username> Add a username to the team list.
|
|
@@ -653,18 +653,25 @@ function parseFrontmatter(text) {
|
|
|
653
653
|
const head = text.slice(4, end).trim();
|
|
654
654
|
const body = text.slice(end + 4).replace(/^\r?\n/, "");
|
|
655
655
|
const fm = { source_sessions: [] };
|
|
656
|
-
let
|
|
656
|
+
let arrayKey = null;
|
|
657
657
|
for (const raw of head.split(/\r?\n/)) {
|
|
658
|
-
if (
|
|
658
|
+
if (arrayKey) {
|
|
659
659
|
const m2 = raw.match(/^\s+-\s+(.+)$/);
|
|
660
660
|
if (m2) {
|
|
661
|
-
fm
|
|
661
|
+
const arr = fm[arrayKey] ?? [];
|
|
662
|
+
arr.push(m2[1].trim());
|
|
663
|
+
fm[arrayKey] = arr;
|
|
662
664
|
continue;
|
|
663
665
|
}
|
|
664
|
-
|
|
666
|
+
arrayKey = null;
|
|
665
667
|
}
|
|
666
668
|
if (raw.startsWith("source_sessions:")) {
|
|
667
|
-
|
|
669
|
+
arrayKey = "source_sessions";
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (raw.startsWith("contributors:")) {
|
|
673
|
+
arrayKey = "contributors";
|
|
674
|
+
fm.contributors = [];
|
|
668
675
|
continue;
|
|
669
676
|
}
|
|
670
677
|
const m = raw.match(/^([a-zA-Z_]+):\s*(.*)$/);
|
|
@@ -879,11 +886,19 @@ function buildPullSql(args) {
|
|
|
879
886
|
where.push(`name = '${esc(args.skillName)}'`);
|
|
880
887
|
}
|
|
881
888
|
const whereClause = where.length > 0 ? ` WHERE ${where.join(" AND ")}` : "";
|
|
882
|
-
|
|
889
|
+
const contributorsCol = args.includeContributors === false ? "" : "contributors, ";
|
|
890
|
+
return `SELECT name, project, project_key, body, version, source_agent, scope, author, ${contributorsCol}description, trigger_text, source_sessions, install, created_at, updated_at FROM "${args.tableName}"${whereClause} ORDER BY project_key ASC, name ASC, version DESC`;
|
|
891
|
+
}
|
|
892
|
+
function isMissingContributorsColumnError(message) {
|
|
893
|
+
if (!message)
|
|
894
|
+
return false;
|
|
895
|
+
return /contributors.*(?:does not exist|not found|unknown)/i.test(message) || /(?:does not exist|unknown column).*contributors/i.test(message);
|
|
883
896
|
}
|
|
884
897
|
function isMissingTableError(message) {
|
|
885
898
|
if (!message)
|
|
886
899
|
return false;
|
|
900
|
+
if (/\bcolumn\b/i.test(message))
|
|
901
|
+
return false;
|
|
887
902
|
return /Table does not exist|relation .* does not exist|no such table/i.test(message);
|
|
888
903
|
}
|
|
889
904
|
function resolvePullDestination(install, cwd) {
|
|
@@ -979,11 +994,16 @@ function selectLatestPerName(rows) {
|
|
|
979
994
|
}
|
|
980
995
|
function renderSkillFile(row) {
|
|
981
996
|
const sources = parseSourceSessions(row.source_sessions);
|
|
997
|
+
const author = typeof row.author === "string" && row.author.length > 0 ? row.author : void 0;
|
|
998
|
+
const contributors = parseContributors(row.contributors);
|
|
999
|
+
const renderedContributors = contributors.length > 0 ? contributors : author ? [author] : [];
|
|
982
1000
|
const fm = {
|
|
983
1001
|
name: String(row.name ?? ""),
|
|
984
1002
|
description: String(row.description ?? ""),
|
|
985
1003
|
trigger: typeof row.trigger_text === "string" && row.trigger_text.length > 0 ? String(row.trigger_text) : void 0,
|
|
1004
|
+
author,
|
|
986
1005
|
source_sessions: sources,
|
|
1006
|
+
contributors: renderedContributors,
|
|
987
1007
|
version: Number(row.version ?? 1),
|
|
988
1008
|
created_by_agent: String(row.source_agent ?? "unknown"),
|
|
989
1009
|
created_at: String(row.created_at ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
@@ -1008,15 +1028,35 @@ function parseSourceSessions(v) {
|
|
|
1008
1028
|
}
|
|
1009
1029
|
return [];
|
|
1010
1030
|
}
|
|
1031
|
+
function parseContributors(v) {
|
|
1032
|
+
if (Array.isArray(v))
|
|
1033
|
+
return v.map(String);
|
|
1034
|
+
if (typeof v === "string") {
|
|
1035
|
+
try {
|
|
1036
|
+
const parsed = JSON.parse(v);
|
|
1037
|
+
if (Array.isArray(parsed))
|
|
1038
|
+
return parsed.map(String);
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return [];
|
|
1043
|
+
}
|
|
1011
1044
|
function renderFrontmatter(fm) {
|
|
1012
1045
|
const lines = ["---"];
|
|
1013
1046
|
lines.push(`name: ${fm.name}`);
|
|
1014
1047
|
lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
1015
1048
|
if (fm.trigger)
|
|
1016
1049
|
lines.push(`trigger: ${JSON.stringify(fm.trigger)}`);
|
|
1050
|
+
if (fm.author)
|
|
1051
|
+
lines.push(`author: ${fm.author}`);
|
|
1017
1052
|
lines.push(`source_sessions:`);
|
|
1018
1053
|
for (const s of fm.source_sessions)
|
|
1019
1054
|
lines.push(` - ${s}`);
|
|
1055
|
+
if (fm.contributors && fm.contributors.length > 0) {
|
|
1056
|
+
lines.push(`contributors:`);
|
|
1057
|
+
for (const c of fm.contributors)
|
|
1058
|
+
lines.push(` - ${c}`);
|
|
1059
|
+
}
|
|
1020
1060
|
lines.push(`version: ${fm.version}`);
|
|
1021
1061
|
lines.push(`created_by_agent: ${fm.created_by_agent}`);
|
|
1022
1062
|
lines.push(`created_at: ${fm.created_at}`);
|
|
@@ -1056,10 +1096,19 @@ async function runPull(opts) {
|
|
|
1056
1096
|
try {
|
|
1057
1097
|
rows = await opts.query(sql);
|
|
1058
1098
|
} catch (e) {
|
|
1059
|
-
if (isMissingTableError(e?.message))
|
|
1099
|
+
if (isMissingTableError(e?.message)) {
|
|
1060
1100
|
rows = [];
|
|
1061
|
-
else
|
|
1101
|
+
} else if (isMissingContributorsColumnError(e?.message)) {
|
|
1102
|
+
const legacySql = buildPullSql({
|
|
1103
|
+
tableName: opts.tableName,
|
|
1104
|
+
users: opts.users,
|
|
1105
|
+
skillName: opts.skillName,
|
|
1106
|
+
includeContributors: false
|
|
1107
|
+
});
|
|
1108
|
+
rows = await opts.query(legacySql);
|
|
1109
|
+
} else {
|
|
1062
1110
|
throw e;
|
|
1111
|
+
}
|
|
1063
1112
|
}
|
|
1064
1113
|
const latest = selectLatestPerName(rows);
|
|
1065
1114
|
const root = resolvePullDestination(opts.install, opts.cwd);
|
|
@@ -1273,7 +1322,7 @@ SKILLS (skillify) \u2014 mine + share reusable skills across the org:
|
|
|
1273
1322
|
- hivemind skillify unpull --user <email> \u2014 remove only that author's pulls
|
|
1274
1323
|
- hivemind skillify unpull --not-mine \u2014 remove all pulls except your own
|
|
1275
1324
|
- hivemind skillify unpull --dry-run \u2014 preview without touching disk
|
|
1276
|
-
- hivemind skillify scope <me|team
|
|
1325
|
+
- hivemind skillify scope <me|team> \u2014 sharing scope for new skills
|
|
1277
1326
|
- hivemind skillify install <project|global> \u2014 default install location
|
|
1278
1327
|
- hivemind skillify team add|remove|list <name> \u2014 manage team list`;
|
|
1279
1328
|
async function main() {
|
|
@@ -90,9 +90,16 @@ function renderFrontmatter(fm) {
|
|
|
90
90
|
lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
91
91
|
if (fm.trigger)
|
|
92
92
|
lines.push(`trigger: ${JSON.stringify(fm.trigger)}`);
|
|
93
|
+
if (fm.author)
|
|
94
|
+
lines.push(`author: ${fm.author}`);
|
|
93
95
|
lines.push(`source_sessions:`);
|
|
94
96
|
for (const s of fm.source_sessions)
|
|
95
97
|
lines.push(` - ${s}`);
|
|
98
|
+
if (fm.contributors && fm.contributors.length > 0) {
|
|
99
|
+
lines.push(`contributors:`);
|
|
100
|
+
for (const c of fm.contributors)
|
|
101
|
+
lines.push(` - ${c}`);
|
|
102
|
+
}
|
|
96
103
|
lines.push(`version: ${fm.version}`);
|
|
97
104
|
lines.push(`created_by_agent: ${fm.created_by_agent}`);
|
|
98
105
|
lines.push(`created_at: ${fm.created_at}`);
|
|
@@ -109,18 +116,25 @@ function parseFrontmatter(text) {
|
|
|
109
116
|
const head = text.slice(4, end).trim();
|
|
110
117
|
const body = text.slice(end + 4).replace(/^\r?\n/, "");
|
|
111
118
|
const fm = { source_sessions: [] };
|
|
112
|
-
let
|
|
119
|
+
let arrayKey = null;
|
|
113
120
|
for (const raw of head.split(/\r?\n/)) {
|
|
114
|
-
if (
|
|
121
|
+
if (arrayKey) {
|
|
115
122
|
const m2 = raw.match(/^\s+-\s+(.+)$/);
|
|
116
123
|
if (m2) {
|
|
117
|
-
fm
|
|
124
|
+
const arr = fm[arrayKey] ?? [];
|
|
125
|
+
arr.push(m2[1].trim());
|
|
126
|
+
fm[arrayKey] = arr;
|
|
118
127
|
continue;
|
|
119
128
|
}
|
|
120
|
-
|
|
129
|
+
arrayKey = null;
|
|
121
130
|
}
|
|
122
131
|
if (raw.startsWith("source_sessions:")) {
|
|
123
|
-
|
|
132
|
+
arrayKey = "source_sessions";
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (raw.startsWith("contributors:")) {
|
|
136
|
+
arrayKey = "contributors";
|
|
137
|
+
fm.contributors = [];
|
|
124
138
|
continue;
|
|
125
139
|
}
|
|
126
140
|
const m = raw.match(/^([a-zA-Z_]+):\s*(.*)$/);
|
|
@@ -151,11 +165,15 @@ function writeNewSkill(args) {
|
|
|
151
165
|
}
|
|
152
166
|
mkdirSync(dir, { recursive: true });
|
|
153
167
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
168
|
+
const author = args.author && args.author.length > 0 ? args.author : void 0;
|
|
169
|
+
const contributors = author ? [author] : [];
|
|
154
170
|
const fm = {
|
|
155
171
|
name: args.name,
|
|
156
172
|
description: args.description,
|
|
157
173
|
trigger: args.trigger,
|
|
174
|
+
author,
|
|
158
175
|
source_sessions: args.sourceSessions,
|
|
176
|
+
contributors,
|
|
159
177
|
version: 1,
|
|
160
178
|
created_by_agent: args.agent,
|
|
161
179
|
created_at: now,
|
|
@@ -166,7 +184,15 @@ function writeNewSkill(args) {
|
|
|
166
184
|
${args.body.trim()}
|
|
167
185
|
`;
|
|
168
186
|
writeFileSync(path, text);
|
|
169
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
path,
|
|
189
|
+
action: "created",
|
|
190
|
+
version: 1,
|
|
191
|
+
createdAt: now,
|
|
192
|
+
updatedAt: now,
|
|
193
|
+
author,
|
|
194
|
+
contributors
|
|
195
|
+
};
|
|
170
196
|
}
|
|
171
197
|
function mergeSkill(args) {
|
|
172
198
|
assertValidSkillName(args.name);
|
|
@@ -179,12 +205,20 @@ function mergeSkill(args) {
|
|
|
179
205
|
const prevVersion = parsed?.fm.version ?? 1;
|
|
180
206
|
const prevSources = parsed?.fm.source_sessions ?? [];
|
|
181
207
|
const merged = Array.from(/* @__PURE__ */ new Set([...prevSources, ...args.newSourceSessions]));
|
|
208
|
+
const author = parsed?.fm.author;
|
|
209
|
+
const prevContribs = parsed?.fm.contributors && parsed.fm.contributors.length > 0 ? parsed.fm.contributors : author ? [author] : [];
|
|
210
|
+
const contributors = [...prevContribs];
|
|
211
|
+
if (args.editor && args.editor.length > 0 && !contributors.includes(args.editor)) {
|
|
212
|
+
contributors.push(args.editor);
|
|
213
|
+
}
|
|
182
214
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
183
215
|
const fm = {
|
|
184
216
|
name: args.name,
|
|
185
217
|
description: args.description ?? parsed?.fm.description ?? "",
|
|
186
218
|
trigger: parsed?.fm.trigger,
|
|
219
|
+
author,
|
|
187
220
|
source_sessions: merged,
|
|
221
|
+
contributors,
|
|
188
222
|
version: prevVersion + 1,
|
|
189
223
|
created_by_agent: parsed?.fm.created_by_agent ?? args.agent,
|
|
190
224
|
created_at: parsed?.fm.created_at ?? now,
|
|
@@ -195,7 +229,15 @@ function mergeSkill(args) {
|
|
|
195
229
|
${args.body.trim()}
|
|
196
230
|
`;
|
|
197
231
|
writeFileSync(path, text);
|
|
198
|
-
return {
|
|
232
|
+
return {
|
|
233
|
+
path,
|
|
234
|
+
action: "merged",
|
|
235
|
+
version: fm.version,
|
|
236
|
+
createdAt: fm.created_at,
|
|
237
|
+
updatedAt: fm.updated_at,
|
|
238
|
+
author,
|
|
239
|
+
contributors
|
|
240
|
+
};
|
|
199
241
|
}
|
|
200
242
|
function listSkills(skillsRoot) {
|
|
201
243
|
if (!existsSync(skillsRoot))
|
|
@@ -220,9 +262,14 @@ function resolveSkillsRoot(install, cwd) {
|
|
|
220
262
|
function listAllExistingSkills(cwd) {
|
|
221
263
|
const projectRoot = resolveSkillsRoot("project", cwd);
|
|
222
264
|
const globalRoot = resolveSkillsRoot("global", cwd);
|
|
265
|
+
const tag = (source) => (s) => {
|
|
266
|
+
const parsed = parseFrontmatter(s.body);
|
|
267
|
+
const author = typeof parsed?.fm.author === "string" && parsed.fm.author.length > 0 ? parsed.fm.author : void 0;
|
|
268
|
+
return { name: s.name, body: s.body, source, author };
|
|
269
|
+
};
|
|
223
270
|
const tagged = [
|
|
224
|
-
...listSkills(projectRoot).map((
|
|
225
|
-
...listSkills(globalRoot).map((
|
|
271
|
+
...listSkills(projectRoot).map(tag("project")),
|
|
272
|
+
...listSkills(globalRoot).map(tag("global"))
|
|
226
273
|
];
|
|
227
274
|
const seen = /* @__PURE__ */ new Set();
|
|
228
275
|
const out = [];
|
|
@@ -242,12 +289,13 @@ function renderExistingSkillsBlock(cwd, charCap) {
|
|
|
242
289
|
block: "(no existing skills \u2014 MERGE is NOT a valid choice; pick KEEP or SKIP only)"
|
|
243
290
|
};
|
|
244
291
|
}
|
|
245
|
-
const mergeTargetNames = skills.filter((s) => s.source === "project").map((s) => s.name);
|
|
246
292
|
let total = 0;
|
|
247
293
|
const out = [];
|
|
294
|
+
const mergeTargetNames = [];
|
|
248
295
|
for (const s of skills) {
|
|
249
|
-
const
|
|
250
|
-
const
|
|
296
|
+
const sourceTag = s.source === "project" ? "project" : "global, read-only";
|
|
297
|
+
const authorTag = s.author ? `, author=${s.author}` : "";
|
|
298
|
+
const block = `--- existing skill [${sourceTag}${authorTag}]: ${s.name} ---
|
|
251
299
|
${s.body}
|
|
252
300
|
`;
|
|
253
301
|
if (total + block.length > charCap) {
|
|
@@ -256,6 +304,8 @@ ${s.body}
|
|
|
256
304
|
}
|
|
257
305
|
out.push(block);
|
|
258
306
|
total += block.length;
|
|
307
|
+
if (s.source === "project")
|
|
308
|
+
mergeTargetNames.push(s.name);
|
|
259
309
|
}
|
|
260
310
|
return { mergeTargetNames, block: out.join("\n") };
|
|
261
311
|
}
|
|
@@ -274,7 +324,11 @@ function sqlIdent(name) {
|
|
|
274
324
|
// dist/src/skillify/skills-table.js
|
|
275
325
|
function createSkillsTableSql(tableName) {
|
|
276
326
|
const safe = sqlIdent(tableName);
|
|
277
|
-
return `CREATE TABLE IF NOT EXISTS "${safe}" (id TEXT NOT NULL DEFAULT '', name TEXT NOT NULL DEFAULT '', project TEXT NOT NULL DEFAULT '', project_key TEXT NOT NULL DEFAULT '', local_path TEXT NOT NULL DEFAULT '', install TEXT NOT NULL DEFAULT 'project', source_sessions TEXT NOT NULL DEFAULT '[]', source_agent TEXT NOT NULL DEFAULT '', scope TEXT NOT NULL DEFAULT 'me', author TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', trigger_text TEXT NOT NULL DEFAULT '', body TEXT NOT NULL DEFAULT '', version BIGINT NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT '') USING deeplake`;
|
|
327
|
+
return `CREATE TABLE IF NOT EXISTS "${safe}" (id TEXT NOT NULL DEFAULT '', name TEXT NOT NULL DEFAULT '', project TEXT NOT NULL DEFAULT '', project_key TEXT NOT NULL DEFAULT '', local_path TEXT NOT NULL DEFAULT '', install TEXT NOT NULL DEFAULT 'project', source_sessions TEXT NOT NULL DEFAULT '[]', source_agent TEXT NOT NULL DEFAULT '', scope TEXT NOT NULL DEFAULT 'me', author TEXT NOT NULL DEFAULT '', contributors TEXT NOT NULL DEFAULT '[]', description TEXT NOT NULL DEFAULT '', trigger_text TEXT NOT NULL DEFAULT '', body TEXT NOT NULL DEFAULT '', version BIGINT NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT '') USING deeplake`;
|
|
328
|
+
}
|
|
329
|
+
function addContributorsColumnSql(tableName) {
|
|
330
|
+
const safe = sqlIdent(tableName);
|
|
331
|
+
return `ALTER TABLE "${safe}" ADD COLUMN IF NOT EXISTS contributors TEXT NOT NULL DEFAULT '[]'`;
|
|
278
332
|
}
|
|
279
333
|
function esc(s) {
|
|
280
334
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
@@ -282,12 +336,20 @@ function esc(s) {
|
|
|
282
336
|
function isMissingTableError(message) {
|
|
283
337
|
if (!message)
|
|
284
338
|
return false;
|
|
339
|
+
if (/\bcolumn\b/i.test(message))
|
|
340
|
+
return false;
|
|
285
341
|
return /Table does not exist|relation .* does not exist|no such table/i.test(message);
|
|
286
342
|
}
|
|
343
|
+
function isMissingContributorsColumnError(message) {
|
|
344
|
+
if (!message)
|
|
345
|
+
return false;
|
|
346
|
+
return /contributors.*(?:does not exist|not found|unknown)/i.test(message) || /(?:does not exist|unknown column).*contributors/i.test(message);
|
|
347
|
+
}
|
|
287
348
|
async function insertSkillRow(args) {
|
|
288
349
|
const id = args.id ?? randomUUID();
|
|
289
350
|
const sourceSessionsJson = JSON.stringify(args.sourceSessions);
|
|
290
|
-
const
|
|
351
|
+
const contributorsJson = JSON.stringify(args.contributors);
|
|
352
|
+
const sql = `INSERT INTO "${sqlIdent(args.tableName)}" (id, name, project, project_key, local_path, install, source_sessions, source_agent, scope, author, contributors, description, trigger_text, body, version, created_at, updated_at) VALUES ('${esc(id)}', '${esc(args.name)}', '${esc(args.project)}', '${esc(args.projectKey)}', '${esc(args.localPath)}', '${esc(args.install)}', '${esc(sourceSessionsJson)}', '${esc(args.sourceAgent)}', '${esc(args.scope)}', '${esc(args.author)}', '${esc(contributorsJson)}', '${esc(args.description)}', '${esc(args.trigger ?? "")}', '${esc(args.body)}', ${args.version}, '${esc(args.createdAt)}', '${esc(args.updatedAt)}')`;
|
|
291
353
|
try {
|
|
292
354
|
await args.query(sql);
|
|
293
355
|
} catch (e) {
|
|
@@ -296,6 +358,11 @@ async function insertSkillRow(args) {
|
|
|
296
358
|
await args.query(sql);
|
|
297
359
|
return;
|
|
298
360
|
}
|
|
361
|
+
if (isMissingContributorsColumnError(e?.message)) {
|
|
362
|
+
await args.query(addContributorsColumnSql(args.tableName));
|
|
363
|
+
await args.query(sql);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
299
366
|
throw e;
|
|
300
367
|
}
|
|
301
368
|
}
|
|
@@ -447,6 +514,14 @@ function runGate(opts) {
|
|
|
447
514
|
}
|
|
448
515
|
}
|
|
449
516
|
|
|
517
|
+
// dist/src/skillify/scope-promotion.js
|
|
518
|
+
function isCrossAuthorMergeVerdict(args) {
|
|
519
|
+
return args.verdict === "MERGE" && args.resultAuthor !== void 0 && args.resultAuthor !== args.userName;
|
|
520
|
+
}
|
|
521
|
+
function resolveRecordScope(args) {
|
|
522
|
+
return args.isCrossAuthorMerge && args.configScope === "me" ? "team" : args.configScope;
|
|
523
|
+
}
|
|
524
|
+
|
|
450
525
|
// dist/src/skillify/state.js
|
|
451
526
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, writeSync, mkdirSync as mkdirSync2, renameSync as renameSync2, existsSync as existsSync4, unlinkSync, openSync, closeSync } from "node:fs";
|
|
452
527
|
import { execSync } from "node:child_process";
|
|
@@ -652,8 +727,6 @@ async function query(sql, retries = 4) {
|
|
|
652
727
|
return [];
|
|
653
728
|
}
|
|
654
729
|
function authorClause() {
|
|
655
|
-
if (cfg.scope === "org")
|
|
656
|
-
return "";
|
|
657
730
|
if (cfg.scope === "team" && cfg.team.length > 0) {
|
|
658
731
|
const list = cfg.team.map((n) => `'${esc2(n)}'`).join(", ");
|
|
659
732
|
return ` AND author IN (${list})`;
|
|
@@ -721,7 +794,7 @@ ${answer}
|
|
|
721
794
|
}
|
|
722
795
|
function buildPrompt(pairs) {
|
|
723
796
|
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
|
|
797
|
+
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 existing skills to merge into. Use KEEP or SKIP only.`;
|
|
725
798
|
return [
|
|
726
799
|
`You are a skill curator for the "${cfg.project}" project. You decide whether the recent`,
|
|
727
800
|
`agent activity below contains a recurring, non-trivial pattern worth crystallizing as a`,
|
|
@@ -731,18 +804,19 @@ function buildPrompt(pairs) {
|
|
|
731
804
|
`- KEEP only if the pattern recurs across at least 3 of the exchanges, is non-obvious to a`,
|
|
732
805
|
` competent engineer, and is not already covered by an existing skill below.`,
|
|
733
806
|
`- SKIP if the activity is one-off, generic engineering work, or already covered.`,
|
|
734
|
-
`- MERGE if the pattern is a meaningful extension of an existing
|
|
807
|
+
`- MERGE if the pattern is a meaningful extension of an existing skill \u2014 produce a`,
|
|
735
808
|
` merged body that incorporates the new evidence without exceeding ~3000 characters or`,
|
|
736
809
|
` covering unrelated domains.`,
|
|
737
810
|
`- ${mergeTargetsClause}`,
|
|
738
|
-
`-
|
|
739
|
-
`
|
|
740
|
-
`
|
|
741
|
-
`
|
|
811
|
+
`- Cross-author MERGE has a real cost: editing a skill authored by someone else is`,
|
|
812
|
+
` recorded as a team-level edit (scope=team, contributors+="${cfg.userName}"). Use it only`,
|
|
813
|
+
` when the new evidence genuinely extends the existing skill; otherwise pick KEEP or SKIP.`,
|
|
814
|
+
` Tags like [project, author=alice] / [global, author=bob] tell you whose skill it is.`,
|
|
742
815
|
`- Skill bodies should follow the existing style: short sections (When to use, Workflow,`,
|
|
743
816
|
` Anti-patterns), concrete commands and file paths drawn from the exchanges, no marketing.`,
|
|
744
817
|
``,
|
|
745
|
-
`=== EXISTING SKILLS (
|
|
818
|
+
`=== EXISTING SKILLS (all MERGE-eligible; [global, author=X] entries from teammate X mean`,
|
|
819
|
+
`cross-author MERGE auto-promotes scope to team) ===`,
|
|
746
820
|
existing.block,
|
|
747
821
|
``,
|
|
748
822
|
`=== RECENT EXCHANGES (prompt + answer pairs, tool calls already stripped) ===`,
|
|
@@ -865,6 +939,17 @@ async function main() {
|
|
|
865
939
|
const watermarkDate = oldest.lastMsg;
|
|
866
940
|
const sourceSessions = usable.map((c) => (c.path.split("/").pop() ?? "").replace(/\.[^.]+$/, ""));
|
|
867
941
|
async function recordToDeeplake(result, verdict2) {
|
|
942
|
+
const author = result.author ?? cfg.userName;
|
|
943
|
+
const isCrossAuthorMerge = isCrossAuthorMergeVerdict({
|
|
944
|
+
verdict: verdict2.verdict,
|
|
945
|
+
resultAuthor: result.author,
|
|
946
|
+
userName: cfg.userName
|
|
947
|
+
});
|
|
948
|
+
const scope = resolveRecordScope({
|
|
949
|
+
configScope: cfg.scope,
|
|
950
|
+
isCrossAuthorMerge
|
|
951
|
+
});
|
|
952
|
+
const contributors = result.contributors;
|
|
868
953
|
try {
|
|
869
954
|
await insertSkillRow({
|
|
870
955
|
query,
|
|
@@ -876,8 +961,9 @@ async function main() {
|
|
|
876
961
|
install: cfg.install,
|
|
877
962
|
sourceSessions,
|
|
878
963
|
sourceAgent: cfg.agent,
|
|
879
|
-
scope
|
|
880
|
-
author
|
|
964
|
+
scope,
|
|
965
|
+
author,
|
|
966
|
+
contributors,
|
|
881
967
|
description: verdict2.description ?? "",
|
|
882
968
|
trigger: verdict2.trigger,
|
|
883
969
|
body: verdict2.body,
|
|
@@ -885,7 +971,7 @@ async function main() {
|
|
|
885
971
|
createdAt: result.createdAt,
|
|
886
972
|
updatedAt: result.updatedAt
|
|
887
973
|
});
|
|
888
|
-
wlog(`recorded to skills table: name=${verdict2.name} v${result.version}`);
|
|
974
|
+
wlog(`recorded to skills table: name=${verdict2.name} v${result.version} author=${author} scope=${scope} contributors=${contributors.length}` + (isCrossAuthorMerge ? " [auto-promoted me->team]" : ""));
|
|
889
975
|
} catch (e) {
|
|
890
976
|
wlog(`skills table insert failed (non-fatal): ${e.message}`);
|
|
891
977
|
}
|
|
@@ -899,7 +985,8 @@ async function main() {
|
|
|
899
985
|
trigger: verdict.trigger,
|
|
900
986
|
body: verdict.body,
|
|
901
987
|
sourceSessions,
|
|
902
|
-
agent: cfg.agent
|
|
988
|
+
agent: cfg.agent,
|
|
989
|
+
author: cfg.userName
|
|
903
990
|
});
|
|
904
991
|
wlog(`wrote new skill: ${result.path}`);
|
|
905
992
|
recordSkill(cfg.projectKey, verdict.name, watermarkUuid, watermarkDate);
|
|
@@ -916,7 +1003,8 @@ async function main() {
|
|
|
916
1003
|
description: verdict.description,
|
|
917
1004
|
body: verdict.body,
|
|
918
1005
|
newSourceSessions: sourceSessions,
|
|
919
|
-
agent: cfg.agent
|
|
1006
|
+
agent: cfg.agent,
|
|
1007
|
+
editor: cfg.userName
|
|
920
1008
|
});
|
|
921
1009
|
wlog(`merged into skill: ${result.path} (v${result.version})`);
|
|
922
1010
|
recordSkill(cfg.projectKey, verdict.name, watermarkUuid, watermarkDate);
|
|
@@ -932,7 +1020,8 @@ async function main() {
|
|
|
932
1020
|
trigger: verdict.trigger,
|
|
933
1021
|
body: verdict.body,
|
|
934
1022
|
sourceSessions,
|
|
935
|
-
agent: cfg.agent
|
|
1023
|
+
agent: cfg.agent,
|
|
1024
|
+
author: cfg.userName
|
|
936
1025
|
});
|
|
937
1026
|
wlog(`wrote new skill (merge fallback): ${result.path}`);
|
|
938
1027
|
recordSkill(cfg.projectKey, verdict.name, watermarkUuid, watermarkDate);
|