token-goat 2.9.29 → 2.9.31
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/README.md +53 -42
- package/SECURITY.md +37 -11
- package/THIRD_PARTY_NOTICES.md +41 -4
- package/dist/native/linux-arm64/tg-hook +0 -0
- package/dist/native/linux-x64/tg-hook +0 -0
- package/dist/native/win32-arm64/tg-hook.exe +0 -0
- package/dist/native/win32-x64/tg-hook.exe +0 -0
- package/dist/ort-wasm-simd-threaded.mjs +59 -0
- package/dist/{token-goat-chunk-SE6E4BKJ.mjs → token-goat-chunk-25TRAWET.mjs} +23 -6
- package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
- package/dist/{token-goat-chunk-UXSXQQIY.mjs → token-goat-chunk-2M7E4PF7.mjs} +43 -14
- package/dist/token-goat-chunk-2UPZGKKY.mjs +188 -0
- package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
- package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
- package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
- package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
- package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
- package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
- package/dist/{token-goat-chunk-2WOUYWLM.mjs → token-goat-chunk-4GILLLWY.mjs} +34 -7
- package/dist/token-goat-chunk-4NAPX7BA.mjs +90 -0
- package/dist/{token-goat-chunk-NMBWLRHW.mjs → token-goat-chunk-4SC7UJYV.mjs} +9981 -11563
- package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
- package/dist/{token-goat-chunk-O6I2XU4P.mjs → token-goat-chunk-4ZBFWLXF.mjs} +310 -441
- package/dist/token-goat-chunk-57BIAXJL.mjs +286 -0
- package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
- package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
- package/dist/token-goat-chunk-5RG5SBL6.mjs +221 -0
- package/dist/{token-goat-chunk-RRNZMM3A.mjs → token-goat-chunk-5Y6ZYL6U.mjs} +21 -2
- package/dist/token-goat-chunk-646E6U44.mjs +398 -0
- package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
- package/dist/{token-goat-chunk-6AK46ZT7.mjs → token-goat-chunk-6DG6HWP3.mjs} +13 -3
- package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
- package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
- package/dist/{token-goat-chunk-E5TK5DNO.mjs → token-goat-chunk-76FGDLRO.mjs} +574 -340
- package/dist/{token-goat-chunk-TGOXNAHW.mjs → token-goat-chunk-7EZTZOMC.mjs} +780 -767
- package/dist/token-goat-chunk-7MAOA6X4.mjs +3647 -0
- package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
- package/dist/{token-goat-chunk-TCQANXYC.mjs → token-goat-chunk-AM5V6GSX.mjs} +18 -2
- package/dist/token-goat-chunk-AUNGYYQX.mjs +184 -0
- package/dist/token-goat-chunk-AUQYRMMH.mjs +2985 -0
- package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
- package/dist/{token-goat-chunk-KOPIR3KZ.mjs → token-goat-chunk-BA4HI6BW.mjs} +1910 -165
- package/dist/{token-goat-chunk-52NSDWYY.mjs → token-goat-chunk-BFCWY73P.mjs} +58 -20
- package/dist/token-goat-chunk-BPGCUMR7.mjs +30 -0
- package/dist/token-goat-chunk-BWGFUZZE.mjs +25 -0
- package/dist/token-goat-chunk-C75NA7PL.mjs +322 -0
- package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
- package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
- package/dist/token-goat-chunk-CIMQ2BB3.mjs +233 -0
- package/dist/{token-goat-chunk-DNSPXQXQ.mjs → token-goat-chunk-CJLS3CDL.mjs} +70 -18
- package/dist/{token-goat-chunk-MELOKFDJ.mjs → token-goat-chunk-CO3UYX6G.mjs} +7 -1
- package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
- package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
- package/dist/token-goat-chunk-CVCEVUGK.mjs +315 -0
- package/dist/token-goat-chunk-CXHOFN7R.mjs +371 -0
- package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
- package/dist/{token-goat-chunk-E4ZOJIZK.mjs → token-goat-chunk-DGS2LVNA.mjs} +70 -160
- package/dist/{token-goat-chunk-L7H3U27L.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
- package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
- package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
- package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
- package/dist/token-goat-chunk-EELTBKDM.mjs +613 -0
- package/dist/token-goat-chunk-EVMZJ27Z.mjs +147 -0
- package/dist/token-goat-chunk-EXKGNHEG.mjs +2096 -0
- package/dist/{token-goat-chunk-YOOVFPTP.mjs → token-goat-chunk-F4HYL64R.mjs} +12 -2
- package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
- package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
- package/dist/{token-goat-chunk-NMTKNYGF.mjs → token-goat-chunk-FNFBKLRD.mjs} +74 -9
- package/dist/token-goat-chunk-G5PJ7AGM.mjs +36 -0
- package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
- package/dist/{token-goat-chunk-TDUT2CL6.mjs → token-goat-chunk-GL3ETKSC.mjs} +276 -19
- package/dist/{token-goat-chunk-7CPMDFGB.mjs → token-goat-chunk-GVB2MUST.mjs} +265 -1567
- package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
- package/dist/token-goat-chunk-HAVAOB3S.mjs +87 -0
- package/dist/token-goat-chunk-HLPW63A7.mjs +48 -0
- package/dist/token-goat-chunk-HU4TORC5.mjs +1377 -0
- package/dist/token-goat-chunk-I7VKZZAW.mjs +35 -0
- package/dist/token-goat-chunk-IA6XSPE3.mjs +218 -0
- package/dist/token-goat-chunk-IAGXMHIK.mjs +863 -0
- package/dist/{token-goat-chunk-435UQTKD.mjs → token-goat-chunk-IK2DRMFX.mjs} +129 -74
- package/dist/{token-goat-chunk-ZER6EAXZ.mjs → token-goat-chunk-INNPTRUX.mjs} +2954 -2783
- package/dist/token-goat-chunk-JJ5BX4F4.mjs +129 -0
- package/dist/token-goat-chunk-JVFSHVFJ.mjs +129 -0
- package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
- package/dist/token-goat-chunk-KKUFXAPD.mjs +25 -0
- package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
- package/dist/token-goat-chunk-LCNQ2CNN.mjs +497 -0
- package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
- package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
- package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
- package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
- package/dist/{token-goat-chunk-2AWQ2R5X.mjs → token-goat-chunk-MHX7XCC3.mjs} +3 -2
- package/dist/token-goat-chunk-MNWKPRAL.mjs +13 -0
- package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
- package/dist/{token-goat-chunk-DPNLUJWB.mjs → token-goat-chunk-N5MMR2IW.mjs} +30 -8
- package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
- package/dist/{token-goat-chunk-VMFWNHGR.mjs → token-goat-chunk-NISNMD7T.mjs} +2545 -2953
- package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
- package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
- package/dist/token-goat-chunk-NSNSPPBG.mjs +69 -0
- package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
- package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
- package/dist/token-goat-chunk-ODBT2USM.mjs +800 -0
- package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
- package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
- package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
- package/dist/{token-goat-chunk-ASLWOQ3I.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -1
- package/dist/{token-goat-chunk-67BNIKKO.mjs → token-goat-chunk-P6HQSWQ3.mjs} +55 -47
- package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
- package/dist/token-goat-chunk-PFWIMH2E.mjs +261 -0
- package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
- package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
- package/dist/token-goat-chunk-PX5PQWTY.mjs +462 -0
- package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
- package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
- package/dist/token-goat-chunk-QH7YVYOY.mjs +3957 -0
- package/dist/token-goat-chunk-QLMUHEPB.mjs +43 -0
- package/dist/{token-goat-chunk-PYSUADX4.mjs → token-goat-chunk-QQOJIHL3.mjs} +50 -14
- package/dist/token-goat-chunk-QQTWJY2O.mjs +85 -0
- package/dist/token-goat-chunk-QYHO5CPH.mjs +93 -0
- package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
- package/dist/token-goat-chunk-RSLPOBKQ.mjs +339 -0
- package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
- package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
- package/dist/{token-goat-chunk-PGTH2U4C.mjs → token-goat-chunk-SOAFZNNZ.mjs} +164 -40
- package/dist/{token-goat-chunk-G23DWHV4.mjs → token-goat-chunk-SRTHT7KV.mjs} +33 -15
- package/dist/token-goat-chunk-T5P5ACNQ.mjs +212 -0
- package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
- package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
- package/dist/token-goat-chunk-TM6MC754.mjs +40 -0
- package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
- package/dist/token-goat-chunk-U2KSB4SG.mjs +56 -0
- package/dist/token-goat-chunk-U4WHMG6Y.mjs +898 -0
- package/dist/token-goat-chunk-UA5V6P3P.mjs +122 -0
- package/dist/token-goat-chunk-UGJHGR5A.mjs +52 -0
- package/dist/{token-goat-chunk-RQGGMPCR.mjs → token-goat-chunk-UOUDVA5U.mjs} +76 -16
- package/dist/{token-goat-chunk-HNFZZHFW.mjs → token-goat-chunk-URRJ33GR.mjs} +22 -4
- package/dist/token-goat-chunk-UZOROKIS.mjs +165 -0
- package/dist/token-goat-chunk-VIHGGGEI.mjs +4234 -0
- package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
- package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
- package/dist/token-goat-chunk-VWZA2H4P.mjs +368 -0
- package/dist/{token-goat-chunk-QO3LXVVR.mjs → token-goat-chunk-VXEXMJDL.mjs} +2964 -3153
- package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
- package/dist/{token-goat-chunk-ONXH6NZ5.mjs → token-goat-chunk-WDH44VAU.mjs} +19 -22
- package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
- package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
- package/dist/{token-goat-chunk-RLP6J7WR.mjs → token-goat-chunk-XLRBNZCC.mjs} +40 -21
- package/dist/{token-goat-chunk-S4VSXURL.mjs → token-goat-chunk-XRKUX6CM.mjs} +4 -0
- package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
- package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
- package/dist/token-goat-chunk-Y2ZTQOCL.mjs +35 -0
- package/dist/{token-goat-chunk-UA5BKWWQ.mjs → token-goat-chunk-YB4VMGK2.mjs} +15 -4
- package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
- package/dist/{token-goat-chunk-PKRWMHVP.mjs → token-goat-chunk-YHVDKQBR.mjs} +389 -221
- package/dist/token-goat-chunk-YJB5XLWI.mjs +26 -0
- package/dist/{token-goat-chunk-PQ53BSCF.mjs → token-goat-chunk-YKB6AQG3.mjs} +53 -31
- package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
- package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
- package/dist/{token-goat-chunk-HUYUHODZ.mjs → token-goat-chunk-YY6AVUFL.mjs} +13246 -12247
- package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
- package/dist/{token-goat-chunk-BGHSSJUA.mjs → token-goat-chunk-ZFOBKUWH.mjs} +1 -1
- package/dist/token-goat-chunk-ZMBL6UVV.mjs +110 -0
- package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
- package/dist/token-goat-hook-client.cjs +87 -23
- package/dist/token-goat-hook-client.mjs +3 -3
- package/dist/token-goat-hook.mjs +76 -31
- package/dist/token-goat.core.mjs +6 -6
- package/docs/C4_RUNTIME_ARCHITECTURE.md +2 -2
- package/docs/cli.md +113 -60
- package/docs/install.md +63 -20
- package/docs/security.md +10 -8
- package/package.json +18 -7
- package/scripts/verify-native-dist.mjs +386 -0
- package/dist/token-goat-chunk-2NNUA2LR.mjs +0 -143
- package/dist/token-goat-chunk-44BM7DDQ.mjs +0 -294
- package/dist/token-goat-chunk-BGYOZ6DO.mjs +0 -24
- package/dist/token-goat-chunk-C5YPNZKQ.mjs +0 -318
- package/dist/token-goat-chunk-CIQ2FTY3.mjs +0 -1990
- package/dist/token-goat-chunk-D4ETHJD6.mjs +0 -246
- package/dist/token-goat-chunk-DNRPXHYH.mjs +0 -1062
- package/dist/token-goat-chunk-EUSDXRXU.mjs +0 -1429
- package/dist/token-goat-chunk-FTP4U4QA.mjs +0 -64
- package/dist/token-goat-chunk-G7IWXCFF.mjs +0 -456
- package/dist/token-goat-chunk-GLXRTTLE.mjs +0 -36
- package/dist/token-goat-chunk-H5J3C56Q.mjs +0 -1511
- package/dist/token-goat-chunk-IVWI2OCK.mjs +0 -368
- package/dist/token-goat-chunk-JTKCQMQA.mjs +0 -418
- package/dist/token-goat-chunk-LZNBQTDK.mjs +0 -26
- package/dist/token-goat-chunk-ORNFMIER.mjs +0 -295
- package/dist/token-goat-chunk-RTMVLHWX.mjs +0 -7113
- package/dist/token-goat-chunk-VQ6F6T7X.mjs +0 -36
- package/dist/token-goat-chunk-VZQEWRVH.mjs +0 -312
- package/dist/token-goat-chunk-XYWPOSCN.mjs +0 -807
- package/dist/token-goat-chunk-YAGLESS3.mjs +0 -23
- package/dist/token-goat-chunk-YKSRAWUU.mjs +0 -33
- package/dist/token-goat-chunk-Z6KBMOBI.mjs +0 -53
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
claudeConfigDir
|
|
5
|
+
} from "./token-goat-chunk-BPGCUMR7.mjs";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_MAX_AGE_MS,
|
|
8
|
+
DEFAULT_MAX_COUNT
|
|
9
|
+
} from "./token-goat-chunk-4ZBFWLXF.mjs";
|
|
10
|
+
import {
|
|
11
|
+
recordStat
|
|
12
|
+
} from "./token-goat-chunk-U4WHMG6Y.mjs";
|
|
13
|
+
import {
|
|
14
|
+
redactSecrets
|
|
15
|
+
} from "./token-goat-chunk-T5P5ACNQ.mjs";
|
|
16
|
+
import {
|
|
17
|
+
shortFingerprint
|
|
18
|
+
} from "./token-goat-chunk-4GBVPH3D.mjs";
|
|
19
|
+
import {
|
|
20
|
+
registerReset
|
|
21
|
+
} from "./token-goat-chunk-YZAAZU2S.mjs";
|
|
22
|
+
import {
|
|
23
|
+
atomicWriteText,
|
|
24
|
+
ensureDirSync,
|
|
25
|
+
isCodeFenceDelimiter,
|
|
26
|
+
stripLower
|
|
27
|
+
} from "./token-goat-chunk-HU4TORC5.mjs";
|
|
28
|
+
import {
|
|
29
|
+
dataDir
|
|
30
|
+
} from "./token-goat-chunk-CO3UYX6G.mjs";
|
|
31
|
+
import {
|
|
32
|
+
init_define_import_meta_env
|
|
33
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
34
|
+
|
|
35
|
+
// src/skill_cache.ts
|
|
36
|
+
init_define_import_meta_env();
|
|
37
|
+
import * as fs from "fs/promises";
|
|
38
|
+
import { resolve } from "path";
|
|
39
|
+
import { readdirSync, readFileSync, existsSync, statSync, unlinkSync } from "node:fs";
|
|
40
|
+
var COMPACT_END_MARKER = "<!-- COMPACT_END -->";
|
|
41
|
+
var SKILLS_OUTPUT_SUBDIR = "skills";
|
|
42
|
+
var _skillOutputsDirOverride = null;
|
|
43
|
+
var _skillsSourceDirOverride = null;
|
|
44
|
+
var _pluginsManifestPathOverride = null;
|
|
45
|
+
registerReset(() => {
|
|
46
|
+
_skillOutputsDirOverride = null;
|
|
47
|
+
_skillsSourceDirOverride = null;
|
|
48
|
+
_pluginsManifestPathOverride = null;
|
|
49
|
+
inProcessHitLockQueues.clear();
|
|
50
|
+
});
|
|
51
|
+
function skillOutputsDir() {
|
|
52
|
+
if (_skillOutputsDirOverride) return _skillOutputsDirOverride;
|
|
53
|
+
return resolve(dataDir(), SKILLS_OUTPUT_SUBDIR);
|
|
54
|
+
}
|
|
55
|
+
function skillsSourceDir() {
|
|
56
|
+
if (_skillsSourceDirOverride) return _skillsSourceDirOverride;
|
|
57
|
+
return resolve(claudeConfigDir(), "skills");
|
|
58
|
+
}
|
|
59
|
+
async function ensureSkillsDir() {
|
|
60
|
+
try {
|
|
61
|
+
ensureDirSync(skillOutputsDir());
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function contentHash(content) {
|
|
66
|
+
return shortFingerprint(content);
|
|
67
|
+
}
|
|
68
|
+
function safeSessionFragment(sessionId) {
|
|
69
|
+
let result = "";
|
|
70
|
+
for (const c of sessionId) {
|
|
71
|
+
if (result.length >= 16) break;
|
|
72
|
+
if (/[a-zA-Z0-9_-]/.test(c)) {
|
|
73
|
+
result += c;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result.slice(0, 16);
|
|
77
|
+
}
|
|
78
|
+
function safeSkillName(skillName) {
|
|
79
|
+
if (!skillName || skillName.length > 128) return null;
|
|
80
|
+
if (!/^[A-Za-z0-9_:-]+$/.test(skillName)) return null;
|
|
81
|
+
return skillName.replace(/[^a-zA-Z0-9_:-]/g, "_");
|
|
82
|
+
}
|
|
83
|
+
function sanitizeSkillId(name) {
|
|
84
|
+
return name.replace(/:/g, "~");
|
|
85
|
+
}
|
|
86
|
+
function sessionSkillPrefix(sessionId, skillName) {
|
|
87
|
+
const safeSession = safeSessionFragment(sessionId);
|
|
88
|
+
return `${safeSession}-${sanitizeSkillId(skillName)}-`;
|
|
89
|
+
}
|
|
90
|
+
function outputIdFor(sessionId, skillName, contentSha) {
|
|
91
|
+
return `${sessionSkillPrefix(sessionId, skillName)}${contentSha}`;
|
|
92
|
+
}
|
|
93
|
+
var LOAD_RECORD_EXT = ".load";
|
|
94
|
+
function readLoadRecordsSync(dir) {
|
|
95
|
+
const records = [];
|
|
96
|
+
let files;
|
|
97
|
+
try {
|
|
98
|
+
files = readdirSync(dir);
|
|
99
|
+
} catch {
|
|
100
|
+
return records;
|
|
101
|
+
}
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
if (!file.endsWith(LOAD_RECORD_EXT)) continue;
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(readFileSync(resolve(dir, file), "utf-8"));
|
|
106
|
+
if (typeof parsed.skillName !== "string" || typeof parsed.bodyOutputId !== "string") continue;
|
|
107
|
+
records.push({ ...parsed, loadId: file.slice(0, -LOAD_RECORD_EXT.length) });
|
|
108
|
+
} catch {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return records;
|
|
113
|
+
}
|
|
114
|
+
async function sessionLoads(safeSession, name) {
|
|
115
|
+
const metas = await listOutputs();
|
|
116
|
+
const own = metas.filter((m) => m.skillName === name && m.outputId.startsWith(`${safeSession}-`));
|
|
117
|
+
const storedIds = new Set(metas.map((m) => m.outputId));
|
|
118
|
+
const recorded = readLoadRecordsSync(skillOutputsDir()).filter(
|
|
119
|
+
(r) => r.skillName === name && r.loadId.startsWith(`${safeSession}-`) && storedIds.has(r.bodyOutputId)
|
|
120
|
+
);
|
|
121
|
+
return [...own, ...recorded].map((e) => ({ bodyBytes: e.bodyBytes, ts: e.ts }));
|
|
122
|
+
}
|
|
123
|
+
function* contentLineEntries(lines) {
|
|
124
|
+
let inCodeBlock = false;
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
const stripped = lines[i].trim();
|
|
127
|
+
if (isCodeFenceDelimiter(stripped)) {
|
|
128
|
+
inCodeBlock = !inCodeBlock;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!inCodeBlock) yield [i, stripped];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function headingLevel(stripped) {
|
|
135
|
+
const match = stripped.match(/^(#{1,6}) (.*)$/);
|
|
136
|
+
if (!match) return 0;
|
|
137
|
+
return match[2].trim() ? match[1].length : 0;
|
|
138
|
+
}
|
|
139
|
+
function collectSectionBody(lines, startIdx, matchedLevel) {
|
|
140
|
+
const bodyLines = [];
|
|
141
|
+
let inBodyCodeBlock = false;
|
|
142
|
+
for (let j = startIdx; j < lines.length; j++) {
|
|
143
|
+
const stripped = lines[j].trim();
|
|
144
|
+
if (isCodeFenceDelimiter(stripped)) {
|
|
145
|
+
inBodyCodeBlock = !inBodyCodeBlock;
|
|
146
|
+
}
|
|
147
|
+
if (!inBodyCodeBlock) {
|
|
148
|
+
const level = headingLevel(stripped);
|
|
149
|
+
if (level >= 1 && level <= matchedLevel) break;
|
|
150
|
+
}
|
|
151
|
+
bodyLines.push(lines[j]);
|
|
152
|
+
}
|
|
153
|
+
return bodyLines;
|
|
154
|
+
}
|
|
155
|
+
function extractCompactFromMarker(body) {
|
|
156
|
+
if (!body || !body.includes(COMPACT_END_MARKER)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const lines = body.split("\n");
|
|
160
|
+
for (const [i, stripped] of contentLineEntries(lines)) {
|
|
161
|
+
if (stripped === COMPACT_END_MARKER) {
|
|
162
|
+
const preMarker = lines.slice(0, i).join("\n").trim();
|
|
163
|
+
return preMarker || null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function parseSectionOrdinal(heading) {
|
|
169
|
+
const match = heading.match(/^(.*?)(?:#(\d+))?$/);
|
|
170
|
+
if (!match) return [heading, 1];
|
|
171
|
+
const baseHeading = match[1] || heading;
|
|
172
|
+
const ordinal = match[2] ? Math.max(1, parseInt(match[2], 10)) : 1;
|
|
173
|
+
return [baseHeading, ordinal];
|
|
174
|
+
}
|
|
175
|
+
function extractNamedSection(body, heading) {
|
|
176
|
+
if (!body || !heading) return null;
|
|
177
|
+
const [baseHeading, ordinal] = parseSectionOrdinal(heading);
|
|
178
|
+
const headingLower = stripLower(baseHeading);
|
|
179
|
+
const lines = body.split("\n");
|
|
180
|
+
let matchCount = 0;
|
|
181
|
+
let startIdx = -1;
|
|
182
|
+
let matchedLevel = 0;
|
|
183
|
+
for (const [i, stripped] of contentLineEntries(lines)) {
|
|
184
|
+
const level = headingLevel(stripped);
|
|
185
|
+
if (level >= 1 && level <= 3) {
|
|
186
|
+
const headingText = stripLower(stripped.slice(level));
|
|
187
|
+
if (headingText === headingLower) {
|
|
188
|
+
matchCount++;
|
|
189
|
+
if (matchCount === ordinal) {
|
|
190
|
+
startIdx = i + 1;
|
|
191
|
+
matchedLevel = level;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (startIdx === -1) return null;
|
|
198
|
+
const text = collectSectionBody(lines, startIdx, matchedLevel).join("\n").trim();
|
|
199
|
+
return text || null;
|
|
200
|
+
}
|
|
201
|
+
function extractChecklistSection(body) {
|
|
202
|
+
if (!body) return null;
|
|
203
|
+
const checklistHeadings = ["checklist", "check list", "to-do", "todo"];
|
|
204
|
+
const lines = body.split("\n");
|
|
205
|
+
let bestPriority = checklistHeadings.length;
|
|
206
|
+
let bestStart = -1;
|
|
207
|
+
for (const [i, stripped] of contentLineEntries(lines)) {
|
|
208
|
+
if (stripped.startsWith("## ") && stripped.length > 3) {
|
|
209
|
+
const headingText = stripLower(stripped.slice(3));
|
|
210
|
+
for (let p = 0; p < checklistHeadings.length; p++) {
|
|
211
|
+
if (headingText.startsWith(checklistHeadings[p])) {
|
|
212
|
+
if (p < bestPriority) {
|
|
213
|
+
bestPriority = p;
|
|
214
|
+
bestStart = i;
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (bestStart === -1) return null;
|
|
222
|
+
const bodyLines = collectSectionBody(lines, bestStart + 1, 2);
|
|
223
|
+
let text = bodyLines.join("\n").trim();
|
|
224
|
+
const maxChars = 2e3;
|
|
225
|
+
if (text.length > maxChars) {
|
|
226
|
+
const cut = maxChars - 1;
|
|
227
|
+
text = text.slice(0, cut).trimEnd() + "\u2026";
|
|
228
|
+
}
|
|
229
|
+
return text || null;
|
|
230
|
+
}
|
|
231
|
+
async function listOutputs() {
|
|
232
|
+
try {
|
|
233
|
+
const dir = skillOutputsDir();
|
|
234
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
235
|
+
const metas = [];
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
if (!entry.isFile() || !entry.name.endsWith(".meta")) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const content = await fs.readFile(resolve(dir, entry.name), "utf-8");
|
|
242
|
+
const meta = JSON.parse(content);
|
|
243
|
+
metas.push(meta);
|
|
244
|
+
} catch {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return metas;
|
|
249
|
+
} catch {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function hasSessionOutput(sessionId, skillName) {
|
|
254
|
+
try {
|
|
255
|
+
if (!sessionId) return false;
|
|
256
|
+
const name = safeSkillName(skillName);
|
|
257
|
+
if (!name) return false;
|
|
258
|
+
return (await sessionLoads(safeSessionFragment(sessionId), name)).length > 0;
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function sessionOutputBodyBytes(sessionId, skillName) {
|
|
264
|
+
try {
|
|
265
|
+
if (!sessionId) return null;
|
|
266
|
+
const name = safeSkillName(skillName);
|
|
267
|
+
if (!name) return null;
|
|
268
|
+
const matches = await sessionLoads(safeSessionFragment(sessionId), name);
|
|
269
|
+
if (matches.length === 0) return null;
|
|
270
|
+
matches.sort((a, b) => b.ts - a.ts);
|
|
271
|
+
return matches[0].bodyBytes;
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async function findCrossSessionEntry(skillName, contentSha) {
|
|
277
|
+
const name = safeSkillName(skillName);
|
|
278
|
+
if (!name || !contentSha) return null;
|
|
279
|
+
try {
|
|
280
|
+
const metas = await listOutputs();
|
|
281
|
+
for (const meta of metas) {
|
|
282
|
+
if (meta.skillName !== name || meta.contentSha !== contentSha) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const dir = skillOutputsDir();
|
|
286
|
+
const bodyPath = resolve(dir, `${meta.outputId}.txt`);
|
|
287
|
+
try {
|
|
288
|
+
const bodyExists = await fs.access(bodyPath).then(() => true).catch(() => false);
|
|
289
|
+
if (bodyExists) {
|
|
290
|
+
return meta;
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
var lastStoreTs = 0;
|
|
302
|
+
function nextStoreTs() {
|
|
303
|
+
lastStoreTs = Math.max(Date.now(), lastStoreTs + 1);
|
|
304
|
+
return lastStoreTs;
|
|
305
|
+
}
|
|
306
|
+
async function storeOutput(sessionId, skillName, body, opts) {
|
|
307
|
+
try {
|
|
308
|
+
await ensureSkillsDir();
|
|
309
|
+
const name = safeSkillName(skillName);
|
|
310
|
+
if (!name) return null;
|
|
311
|
+
const sha = contentHash(body);
|
|
312
|
+
const outId = outputIdFor(sessionId, skillName, sha);
|
|
313
|
+
const dir = skillOutputsDir();
|
|
314
|
+
const ts = nextStoreTs();
|
|
315
|
+
const existing = await findCrossSessionEntry(skillName, sha);
|
|
316
|
+
if (existing) {
|
|
317
|
+
if (existing.outputId !== outId && sessionId) {
|
|
318
|
+
const record = {
|
|
319
|
+
skillName: name,
|
|
320
|
+
contentSha: sha,
|
|
321
|
+
bodyOutputId: existing.outputId,
|
|
322
|
+
bodyBytes: Buffer.byteLength(body, "utf-8"),
|
|
323
|
+
ts
|
|
324
|
+
};
|
|
325
|
+
await atomicWriteText(resolve(dir, `${outId}${LOAD_RECORD_EXT}`), JSON.stringify(record, null, 2));
|
|
326
|
+
}
|
|
327
|
+
return existing;
|
|
328
|
+
}
|
|
329
|
+
const bodyBytes = Buffer.byteLength(body, "utf-8");
|
|
330
|
+
const truncated = bodyBytes > 256 * 1024;
|
|
331
|
+
const redactedBody = redactSecrets(body);
|
|
332
|
+
if (redactedBody.count > 0) recordStat("secret_redacted", 0, redactedBody.count, void 0, SKILLS_OUTPUT_SUBDIR);
|
|
333
|
+
let storedBody = redactedBody.text;
|
|
334
|
+
if (truncated) {
|
|
335
|
+
const buf = Buffer.from(redactedBody.text, "utf-8");
|
|
336
|
+
let truncStart = Math.max(0, buf.length - 262144);
|
|
337
|
+
if (truncStart < buf.length) {
|
|
338
|
+
const byte = buf[truncStart];
|
|
339
|
+
if ((byte & 192) === 128) {
|
|
340
|
+
let charStart = truncStart - 1;
|
|
341
|
+
while (charStart >= 0 && (buf[charStart] & 192) === 128) {
|
|
342
|
+
charStart--;
|
|
343
|
+
}
|
|
344
|
+
if (charStart >= 0) {
|
|
345
|
+
const leadByte = buf[charStart];
|
|
346
|
+
let charLen = 1;
|
|
347
|
+
if ((leadByte & 128) === 0) charLen = 1;
|
|
348
|
+
else if ((leadByte & 224) === 192) charLen = 2;
|
|
349
|
+
else if ((leadByte & 240) === 224) charLen = 3;
|
|
350
|
+
else if ((leadByte & 248) === 240) charLen = 4;
|
|
351
|
+
truncStart = charStart + charLen;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const truncBuf = buf.slice(truncStart);
|
|
356
|
+
storedBody = truncBuf.toString("utf-8");
|
|
357
|
+
}
|
|
358
|
+
await atomicWriteText(resolve(dir, `${outId}.txt`), storedBody);
|
|
359
|
+
const meta = {
|
|
360
|
+
outputId: outId,
|
|
361
|
+
skillName: name,
|
|
362
|
+
contentSha: sha,
|
|
363
|
+
bodyBytes,
|
|
364
|
+
ts,
|
|
365
|
+
truncated,
|
|
366
|
+
sourcePath: opts?.sourcePath || ""
|
|
367
|
+
};
|
|
368
|
+
await atomicWriteText(resolve(dir, `${outId}.meta`), JSON.stringify(meta, null, 2));
|
|
369
|
+
pruneSkillOutputs();
|
|
370
|
+
return meta;
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async function storeCompact(sessionId, skillName, compactText, sourceSha) {
|
|
376
|
+
try {
|
|
377
|
+
await ensureSkillsDir();
|
|
378
|
+
const name = safeSkillName(skillName);
|
|
379
|
+
if (!name) return;
|
|
380
|
+
const safeSession = safeSessionFragment(sessionId);
|
|
381
|
+
const fileId = `${safeSession}@${sanitizeSkillId(name)}@compact`;
|
|
382
|
+
const dir = skillOutputsDir();
|
|
383
|
+
const redacted = redactSecrets(compactText);
|
|
384
|
+
if (redacted.count > 0) recordStat("secret_redacted", 0, redacted.count, void 0, SKILLS_OUTPUT_SUBDIR);
|
|
385
|
+
let text = redacted.text;
|
|
386
|
+
if (sourceSha) {
|
|
387
|
+
text = `<!-- source_sha: ${sourceSha.slice(0, 12)} -->
|
|
388
|
+
${text}`;
|
|
389
|
+
}
|
|
390
|
+
await atomicWriteText(resolve(dir, fileId), text);
|
|
391
|
+
} catch {
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function compactSessionSuffix(skillName) {
|
|
395
|
+
const name = safeSkillName(skillName);
|
|
396
|
+
return name ? `@${sanitizeSkillId(name)}@compact` : null;
|
|
397
|
+
}
|
|
398
|
+
function matchesCompactSuffix(entryName, isFile, suffix) {
|
|
399
|
+
return isFile && entryName.endsWith("@compact") && entryName.endsWith(suffix);
|
|
400
|
+
}
|
|
401
|
+
function getCompactAnySessionSync(skillName) {
|
|
402
|
+
try {
|
|
403
|
+
const suffix = compactSessionSuffix(skillName);
|
|
404
|
+
if (!suffix) return null;
|
|
405
|
+
const dir = skillOutputsDir();
|
|
406
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
if (!matchesCompactSuffix(entry.name, entry.isFile(), suffix)) continue;
|
|
409
|
+
try {
|
|
410
|
+
const text = readFileSync(resolve(dir, entry.name), "utf-8");
|
|
411
|
+
if (text.trim()) return text;
|
|
412
|
+
} catch {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function extractSourceShaFromCompact(compactText) {
|
|
422
|
+
if (!compactText) return null;
|
|
423
|
+
const match = compactText.match(/<!--\s*source_sha:\s*([a-f0-9]{12})\s*-->/);
|
|
424
|
+
return match ? match[1] : null;
|
|
425
|
+
}
|
|
426
|
+
function isCompactStale(compactText, skillName, currentBodySha) {
|
|
427
|
+
if (!compactText) return null;
|
|
428
|
+
const embeddedSha = extractSourceShaFromCompact(compactText);
|
|
429
|
+
if (!embeddedSha) return null;
|
|
430
|
+
const currentSha = currentBodySha.slice(0, 12);
|
|
431
|
+
return embeddedSha !== currentSha;
|
|
432
|
+
}
|
|
433
|
+
async function readSkillHits(skillName) {
|
|
434
|
+
try {
|
|
435
|
+
const name = safeSkillName(skillName);
|
|
436
|
+
if (!name) return { count: 0, lastTs: 0 };
|
|
437
|
+
const dir = skillOutputsDir();
|
|
438
|
+
const hitsFile = resolve(dir, `${sanitizeSkillId(name)}.hits`);
|
|
439
|
+
const content = await fs.readFile(hitsFile, "utf-8").catch(() => null);
|
|
440
|
+
if (content) {
|
|
441
|
+
const parsed = JSON.parse(content);
|
|
442
|
+
return parsed;
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
return { count: 0, lastTs: 0 };
|
|
447
|
+
}
|
|
448
|
+
var SKILL_HIT_LOCK_RETRY_MS = 5;
|
|
449
|
+
var SKILL_HIT_LOCK_STALE_MS = 5e3;
|
|
450
|
+
var SKILL_HIT_LOCK_MAX_ATTEMPTS = Math.ceil(SKILL_HIT_LOCK_STALE_MS / SKILL_HIT_LOCK_RETRY_MS);
|
|
451
|
+
var inProcessHitLockQueues = /* @__PURE__ */ new Map();
|
|
452
|
+
function runExclusiveInProcess(key, fn) {
|
|
453
|
+
const prior = inProcessHitLockQueues.get(key) ?? Promise.resolve();
|
|
454
|
+
const run = prior.then(fn, fn);
|
|
455
|
+
inProcessHitLockQueues.set(
|
|
456
|
+
key,
|
|
457
|
+
run.then(
|
|
458
|
+
() => void 0,
|
|
459
|
+
() => void 0
|
|
460
|
+
)
|
|
461
|
+
);
|
|
462
|
+
return run;
|
|
463
|
+
}
|
|
464
|
+
async function acquireSkillHitLock(lockPath) {
|
|
465
|
+
for (let attempt = 0; attempt < SKILL_HIT_LOCK_MAX_ATTEMPTS; attempt++) {
|
|
466
|
+
try {
|
|
467
|
+
await fs.mkdir(lockPath);
|
|
468
|
+
return true;
|
|
469
|
+
} catch (err) {
|
|
470
|
+
if (err.code !== "EEXIST") return false;
|
|
471
|
+
try {
|
|
472
|
+
const st = await fs.stat(lockPath);
|
|
473
|
+
if (Date.now() - st.mtimeMs > SKILL_HIT_LOCK_STALE_MS) {
|
|
474
|
+
await fs.rmdir(lockPath).catch(() => {
|
|
475
|
+
});
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
} catch {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
await new Promise((r) => setTimeout(r, SKILL_HIT_LOCK_RETRY_MS));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
async function incrementSkillHit(skillName) {
|
|
487
|
+
try {
|
|
488
|
+
const name = safeSkillName(skillName);
|
|
489
|
+
if (!name) return;
|
|
490
|
+
await ensureSkillsDir();
|
|
491
|
+
const dir = skillOutputsDir();
|
|
492
|
+
const hitsFile = resolve(dir, `${sanitizeSkillId(name)}.hits`);
|
|
493
|
+
const lockPath = `${hitsFile}.lock`;
|
|
494
|
+
await runExclusiveInProcess(lockPath, async () => {
|
|
495
|
+
const locked = await acquireSkillHitLock(lockPath);
|
|
496
|
+
if (!locked) return;
|
|
497
|
+
try {
|
|
498
|
+
const hits = await readSkillHits(name);
|
|
499
|
+
hits.count++;
|
|
500
|
+
hits.lastTs = Date.now();
|
|
501
|
+
await atomicWriteText(hitsFile, JSON.stringify(hits, null, 2));
|
|
502
|
+
} finally {
|
|
503
|
+
await fs.rmdir(lockPath).catch(() => {
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function formatAge(ageMs) {
|
|
511
|
+
const s = Math.floor(ageMs / 1e3);
|
|
512
|
+
if (s < 60) return `${s}s`;
|
|
513
|
+
const m = Math.floor(s / 60);
|
|
514
|
+
if (m < 60) return `${m}m`;
|
|
515
|
+
const h = Math.floor(m / 60);
|
|
516
|
+
if (h < 24) return `${h}h`;
|
|
517
|
+
const d = Math.floor(h / 24);
|
|
518
|
+
return `${d}d`;
|
|
519
|
+
}
|
|
520
|
+
async function listSkills(sessionId) {
|
|
521
|
+
try {
|
|
522
|
+
const metas = (await listOutputs()).sort((a, b) => b.ts - a.ts);
|
|
523
|
+
const results = [];
|
|
524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
525
|
+
for (const meta of metas) {
|
|
526
|
+
if (sessionId && !meta.outputId.startsWith(`${safeSessionFragment(sessionId)}-`)) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (seen.has(meta.skillName)) continue;
|
|
530
|
+
seen.add(meta.skillName);
|
|
531
|
+
const dir = skillOutputsDir();
|
|
532
|
+
const sanitizedName = sanitizeSkillId(meta.skillName);
|
|
533
|
+
const outputSuffix = `-${sanitizedName}-${meta.contentSha}`;
|
|
534
|
+
const safeSession = meta.outputId.endsWith(outputSuffix) ? meta.outputId.slice(0, meta.outputId.length - outputSuffix.length) : safeSessionFragment(meta.outputId.split("-")[0]);
|
|
535
|
+
const compactFileId = `${safeSession}@${sanitizedName}@compact`;
|
|
536
|
+
let compactLen = 0;
|
|
537
|
+
let compactText = "";
|
|
538
|
+
try {
|
|
539
|
+
const stat2 = await fs.stat(resolve(dir, compactFileId));
|
|
540
|
+
compactLen = stat2.size;
|
|
541
|
+
compactText = await fs.readFile(resolve(dir, compactFileId), "utf-8").catch(() => "");
|
|
542
|
+
} catch {
|
|
543
|
+
compactLen = 0;
|
|
544
|
+
}
|
|
545
|
+
const hasMarker = extractCompactFromMarker(
|
|
546
|
+
await fs.readFile(resolve(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
|
|
547
|
+
) !== null;
|
|
548
|
+
const compactStale = isCompactStale(compactText, meta.skillName, meta.contentSha);
|
|
549
|
+
const { count: hitCount } = await readSkillHits(meta.skillName);
|
|
550
|
+
const ageMs = Date.now() - meta.ts;
|
|
551
|
+
results.push({
|
|
552
|
+
name: meta.skillName,
|
|
553
|
+
bodyLen: meta.bodyBytes,
|
|
554
|
+
compactLen,
|
|
555
|
+
hasMarker,
|
|
556
|
+
compactStale,
|
|
557
|
+
hitCount,
|
|
558
|
+
ageMs
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
return results;
|
|
562
|
+
} catch {
|
|
563
|
+
return [];
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async function getSkillFilePath(skillName) {
|
|
567
|
+
try {
|
|
568
|
+
const name = safeSkillName(skillName);
|
|
569
|
+
if (!name) return null;
|
|
570
|
+
const metas = await listOutputs();
|
|
571
|
+
for (const meta of metas) {
|
|
572
|
+
if (meta.skillName === name && meta.sourcePath) {
|
|
573
|
+
return meta.sourcePath;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return await installedSkillPath(name);
|
|
577
|
+
} catch {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function resolvePluginSkillPath(pluginName, skillSlug) {
|
|
582
|
+
const manifestPath = _pluginsManifestPathOverride ?? resolve(claudeConfigDir(), "plugins", "installed_plugins.json");
|
|
583
|
+
let raw;
|
|
584
|
+
try {
|
|
585
|
+
raw = await fs.readFile(manifestPath, "utf8");
|
|
586
|
+
} catch {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
let parsed;
|
|
590
|
+
try {
|
|
591
|
+
parsed = JSON.parse(raw);
|
|
592
|
+
} catch {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
596
|
+
const plugins = parsed["plugins"];
|
|
597
|
+
if (typeof plugins !== "object" || plugins === null) return null;
|
|
598
|
+
for (const [key, entries] of Object.entries(plugins)) {
|
|
599
|
+
if (key !== pluginName && !key.startsWith(pluginName + "@")) continue;
|
|
600
|
+
if (!Array.isArray(entries)) continue;
|
|
601
|
+
for (const entry of entries) {
|
|
602
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
603
|
+
const installPath = entry["installPath"];
|
|
604
|
+
if (typeof installPath !== "string" || installPath === "") continue;
|
|
605
|
+
const diskPath = resolve(installPath, "skills", skillSlug, "SKILL.md");
|
|
606
|
+
try {
|
|
607
|
+
await fs.access(diskPath);
|
|
608
|
+
return diskPath;
|
|
609
|
+
} catch {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
async function installedSkillPath(skillName) {
|
|
617
|
+
const name = safeSkillName(skillName);
|
|
618
|
+
if (!name) return null;
|
|
619
|
+
const colonIdx = name.indexOf(":");
|
|
620
|
+
if (colonIdx > 0 && colonIdx < name.length - 1) {
|
|
621
|
+
const pluginPath = await resolvePluginSkillPath(name.slice(0, colonIdx), name.slice(colonIdx + 1));
|
|
622
|
+
if (pluginPath !== null) return pluginPath;
|
|
623
|
+
}
|
|
624
|
+
const diskPath = resolve(skillsSourceDir(), name, "SKILL.md");
|
|
625
|
+
try {
|
|
626
|
+
await fs.access(diskPath);
|
|
627
|
+
return diskPath;
|
|
628
|
+
} catch {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
633
|
+
const dir = skillOutputsDir();
|
|
634
|
+
let removed = 0;
|
|
635
|
+
try {
|
|
636
|
+
if (!existsSync(dir)) return 0;
|
|
637
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
638
|
+
const entries = [];
|
|
639
|
+
for (const file of readdirSync(dir)) {
|
|
640
|
+
if (!file.endsWith(".meta")) continue;
|
|
641
|
+
const outputId = file.slice(0, -".meta".length);
|
|
642
|
+
let ts;
|
|
643
|
+
try {
|
|
644
|
+
const parsed = JSON.parse(readFileSync(resolve(dir, file), "utf-8"));
|
|
645
|
+
ts = parsed.ts;
|
|
646
|
+
} catch {
|
|
647
|
+
try {
|
|
648
|
+
ts = statSync(resolve(dir, file)).mtimeMs;
|
|
649
|
+
} catch {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
entries.push({ outputId, ts });
|
|
654
|
+
}
|
|
655
|
+
const removeEntry = (outputId) => {
|
|
656
|
+
for (const ext of [".meta", ".txt"]) {
|
|
657
|
+
try {
|
|
658
|
+
unlinkSync(resolve(dir, `${outputId}${ext}`));
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
removed++;
|
|
663
|
+
};
|
|
664
|
+
const kept = [];
|
|
665
|
+
for (const entry of entries) {
|
|
666
|
+
if (entry.ts < cutoff) {
|
|
667
|
+
removeEntry(entry.outputId);
|
|
668
|
+
} else {
|
|
669
|
+
kept.push(entry);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (kept.length > maxCount) {
|
|
673
|
+
kept.sort((a, b) => a.ts - b.ts);
|
|
674
|
+
for (const entry of kept.slice(0, kept.length - maxCount)) {
|
|
675
|
+
removeEntry(entry.outputId);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
for (const record of readLoadRecordsSync(dir)) {
|
|
679
|
+
if (record.ts >= cutoff && existsSync(resolve(dir, `${record.bodyOutputId}.meta`))) continue;
|
|
680
|
+
try {
|
|
681
|
+
unlinkSync(resolve(dir, `${record.loadId}${LOAD_RECORD_EXT}`));
|
|
682
|
+
} catch {
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
} catch {
|
|
686
|
+
return removed;
|
|
687
|
+
}
|
|
688
|
+
return removed;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export {
|
|
692
|
+
SKILLS_OUTPUT_SUBDIR,
|
|
693
|
+
skillOutputsDir,
|
|
694
|
+
contentHash,
|
|
695
|
+
safeSkillName,
|
|
696
|
+
extractCompactFromMarker,
|
|
697
|
+
extractNamedSection,
|
|
698
|
+
extractChecklistSection,
|
|
699
|
+
listOutputs,
|
|
700
|
+
hasSessionOutput,
|
|
701
|
+
sessionOutputBodyBytes,
|
|
702
|
+
storeOutput,
|
|
703
|
+
storeCompact,
|
|
704
|
+
getCompactAnySessionSync,
|
|
705
|
+
isCompactStale,
|
|
706
|
+
incrementSkillHit,
|
|
707
|
+
formatAge,
|
|
708
|
+
listSkills,
|
|
709
|
+
getSkillFilePath,
|
|
710
|
+
installedSkillPath,
|
|
711
|
+
pruneSkillOutputs
|
|
712
|
+
};
|