@deeplake/hivemind 0.7.22 → 0.7.24
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 +383 -254
- 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
|
@@ -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);
|
package/hermes/bundle/capture.js
CHANGED
|
@@ -1458,7 +1458,7 @@ function loadScopeConfig() {
|
|
|
1458
1458
|
return DEFAULT;
|
|
1459
1459
|
try {
|
|
1460
1460
|
const raw = JSON.parse(readFileSync7(CONFIG_PATH, "utf-8"));
|
|
1461
|
-
const scope = raw.scope === "team"
|
|
1461
|
+
const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
|
|
1462
1462
|
const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
|
|
1463
1463
|
const install = raw.install === "global" ? "global" : "project";
|
|
1464
1464
|
return { scope, team, install };
|
|
@@ -572,7 +572,7 @@ function loadScopeConfig() {
|
|
|
572
572
|
return DEFAULT;
|
|
573
573
|
try {
|
|
574
574
|
const raw = JSON.parse(readFileSync5(CONFIG_PATH, "utf-8"));
|
|
575
|
-
const scope = raw.scope === "team"
|
|
575
|
+
const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
|
|
576
576
|
const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
|
|
577
577
|
const install = raw.install === "global" ? "global" : "project";
|
|
578
578
|
return { scope, team, install };
|
|
@@ -704,18 +704,25 @@ function parseFrontmatter(text) {
|
|
|
704
704
|
const head = text.slice(4, end).trim();
|
|
705
705
|
const body = text.slice(end + 4).replace(/^\r?\n/, "");
|
|
706
706
|
const fm = { source_sessions: [] };
|
|
707
|
-
let
|
|
707
|
+
let arrayKey = null;
|
|
708
708
|
for (const raw of head.split(/\r?\n/)) {
|
|
709
|
-
if (
|
|
709
|
+
if (arrayKey) {
|
|
710
710
|
const m2 = raw.match(/^\s+-\s+(.+)$/);
|
|
711
711
|
if (m2) {
|
|
712
|
-
fm
|
|
712
|
+
const arr = fm[arrayKey] ?? [];
|
|
713
|
+
arr.push(m2[1].trim());
|
|
714
|
+
fm[arrayKey] = arr;
|
|
713
715
|
continue;
|
|
714
716
|
}
|
|
715
|
-
|
|
717
|
+
arrayKey = null;
|
|
716
718
|
}
|
|
717
719
|
if (raw.startsWith("source_sessions:")) {
|
|
718
|
-
|
|
720
|
+
arrayKey = "source_sessions";
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
if (raw.startsWith("contributors:")) {
|
|
724
|
+
arrayKey = "contributors";
|
|
725
|
+
fm.contributors = [];
|
|
719
726
|
continue;
|
|
720
727
|
}
|
|
721
728
|
const m = raw.match(/^([a-zA-Z_]+):\s*(.*)$/);
|
|
@@ -930,11 +937,19 @@ function buildPullSql(args) {
|
|
|
930
937
|
where.push(`name = '${esc(args.skillName)}'`);
|
|
931
938
|
}
|
|
932
939
|
const whereClause = where.length > 0 ? ` WHERE ${where.join(" AND ")}` : "";
|
|
933
|
-
|
|
940
|
+
const contributorsCol = args.includeContributors === false ? "" : "contributors, ";
|
|
941
|
+
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`;
|
|
942
|
+
}
|
|
943
|
+
function isMissingContributorsColumnError(message) {
|
|
944
|
+
if (!message)
|
|
945
|
+
return false;
|
|
946
|
+
return /contributors.*(?:does not exist|not found|unknown)/i.test(message) || /(?:does not exist|unknown column).*contributors/i.test(message);
|
|
934
947
|
}
|
|
935
948
|
function isMissingTableError(message) {
|
|
936
949
|
if (!message)
|
|
937
950
|
return false;
|
|
951
|
+
if (/\bcolumn\b/i.test(message))
|
|
952
|
+
return false;
|
|
938
953
|
return /Table does not exist|relation .* does not exist|no such table/i.test(message);
|
|
939
954
|
}
|
|
940
955
|
function resolvePullDestination(install, cwd) {
|
|
@@ -1030,11 +1045,16 @@ function selectLatestPerName(rows) {
|
|
|
1030
1045
|
}
|
|
1031
1046
|
function renderSkillFile(row) {
|
|
1032
1047
|
const sources = parseSourceSessions(row.source_sessions);
|
|
1048
|
+
const author = typeof row.author === "string" && row.author.length > 0 ? row.author : void 0;
|
|
1049
|
+
const contributors = parseContributors(row.contributors);
|
|
1050
|
+
const renderedContributors = contributors.length > 0 ? contributors : author ? [author] : [];
|
|
1033
1051
|
const fm = {
|
|
1034
1052
|
name: String(row.name ?? ""),
|
|
1035
1053
|
description: String(row.description ?? ""),
|
|
1036
1054
|
trigger: typeof row.trigger_text === "string" && row.trigger_text.length > 0 ? String(row.trigger_text) : void 0,
|
|
1055
|
+
author,
|
|
1037
1056
|
source_sessions: sources,
|
|
1057
|
+
contributors: renderedContributors,
|
|
1038
1058
|
version: Number(row.version ?? 1),
|
|
1039
1059
|
created_by_agent: String(row.source_agent ?? "unknown"),
|
|
1040
1060
|
created_at: String(row.created_at ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
@@ -1059,15 +1079,35 @@ function parseSourceSessions(v) {
|
|
|
1059
1079
|
}
|
|
1060
1080
|
return [];
|
|
1061
1081
|
}
|
|
1082
|
+
function parseContributors(v) {
|
|
1083
|
+
if (Array.isArray(v))
|
|
1084
|
+
return v.map(String);
|
|
1085
|
+
if (typeof v === "string") {
|
|
1086
|
+
try {
|
|
1087
|
+
const parsed = JSON.parse(v);
|
|
1088
|
+
if (Array.isArray(parsed))
|
|
1089
|
+
return parsed.map(String);
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return [];
|
|
1094
|
+
}
|
|
1062
1095
|
function renderFrontmatter(fm) {
|
|
1063
1096
|
const lines = ["---"];
|
|
1064
1097
|
lines.push(`name: ${fm.name}`);
|
|
1065
1098
|
lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
1066
1099
|
if (fm.trigger)
|
|
1067
1100
|
lines.push(`trigger: ${JSON.stringify(fm.trigger)}`);
|
|
1101
|
+
if (fm.author)
|
|
1102
|
+
lines.push(`author: ${fm.author}`);
|
|
1068
1103
|
lines.push(`source_sessions:`);
|
|
1069
1104
|
for (const s of fm.source_sessions)
|
|
1070
1105
|
lines.push(` - ${s}`);
|
|
1106
|
+
if (fm.contributors && fm.contributors.length > 0) {
|
|
1107
|
+
lines.push(`contributors:`);
|
|
1108
|
+
for (const c of fm.contributors)
|
|
1109
|
+
lines.push(` - ${c}`);
|
|
1110
|
+
}
|
|
1071
1111
|
lines.push(`version: ${fm.version}`);
|
|
1072
1112
|
lines.push(`created_by_agent: ${fm.created_by_agent}`);
|
|
1073
1113
|
lines.push(`created_at: ${fm.created_at}`);
|
|
@@ -1107,10 +1147,19 @@ async function runPull(opts) {
|
|
|
1107
1147
|
try {
|
|
1108
1148
|
rows = await opts.query(sql);
|
|
1109
1149
|
} catch (e) {
|
|
1110
|
-
if (isMissingTableError(e?.message))
|
|
1150
|
+
if (isMissingTableError(e?.message)) {
|
|
1111
1151
|
rows = [];
|
|
1112
|
-
else
|
|
1152
|
+
} else if (isMissingContributorsColumnError(e?.message)) {
|
|
1153
|
+
const legacySql = buildPullSql({
|
|
1154
|
+
tableName: opts.tableName,
|
|
1155
|
+
users: opts.users,
|
|
1156
|
+
skillName: opts.skillName,
|
|
1157
|
+
includeContributors: false
|
|
1158
|
+
});
|
|
1159
|
+
rows = await opts.query(legacySql);
|
|
1160
|
+
} else {
|
|
1113
1161
|
throw e;
|
|
1162
|
+
}
|
|
1114
1163
|
}
|
|
1115
1164
|
const latest = selectLatestPerName(rows);
|
|
1116
1165
|
const root = resolvePullDestination(opts.install, opts.cwd);
|
|
@@ -1314,7 +1363,7 @@ SKILLS (skillify) \u2014 mine + share reusable skills across the org:
|
|
|
1314
1363
|
- hivemind skillify unpull --user <email> \u2014 remove only that author's pulls
|
|
1315
1364
|
- hivemind skillify unpull --not-mine \u2014 remove all pulls except your own
|
|
1316
1365
|
- hivemind skillify unpull --dry-run \u2014 preview without touching disk
|
|
1317
|
-
- hivemind skillify scope <me|team
|
|
1366
|
+
- hivemind skillify scope <me|team> \u2014 sharing scope for new skills
|
|
1318
1367
|
- hivemind skillify install <project|global> \u2014 default install location
|
|
1319
1368
|
- hivemind skillify team add|remove|list <name> \u2014 manage team list`;
|
|
1320
1369
|
async function createPlaceholder(api, table, sessionId, cwd, userName, orgName, workspaceId, pluginVersion) {
|