skillwiki 0.10.26 → 0.10.28
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/dist/{chunk-EQPU2BPM.js → chunk-23CR4YPL.js} +6 -8
- package/dist/chunk-5XQWYC5K.js +5255 -0
- package/dist/chunk-NOXDRIHM.js +1194 -0
- package/dist/chunk-OJVIND5D.js +126 -0
- package/dist/{chunk-C2DKFJFA.js → chunk-RQARJ6HB.js} +286 -1
- package/dist/{chunk-QNTBNNEL.js → chunk-SGZPIGGX.js} +12 -14
- package/dist/{chunk-QBZYEEBD.js → chunk-UI4SOWI2.js} +7 -123
- package/dist/{chunk-VXMHHXVP.js → chunk-W7NPTDIU.js} +463 -4745
- package/dist/cli.js +107 -703
- package/dist/{index-projection-FRWTZB5Y.js → index-projection-64WW36N3.js} +3 -3
- package/dist/{managed-write-preflight-4LPVMP42.js → managed-write-preflight-2FU5F56H.js} +5 -3
- package/dist/skillwiki-mcp.js +6 -6
- package/dist/sources-7UC6PJEV.js +9 -0
- package/dist/vault-sync/scripts/lib/platform.sh +264 -0
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/dist/chunk-6ZDKTNLA.js +0 -294
- package/dist/chunk-BASWDOQB.js +0 -455
- package/dist/chunk-PQG26AGJ.js +0 -941
- package/dist/sources-L2SQV63E.js +0 -10
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
UNMANAGED_START,
|
|
5
5
|
renderRootIndex,
|
|
6
6
|
writeRootIndexProjection
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-OJVIND5D.js";
|
|
8
|
+
import "./chunk-UI4SOWI2.js";
|
|
9
|
+
import "./chunk-RQARJ6HB.js";
|
|
10
10
|
export {
|
|
11
11
|
UNMANAGED_END,
|
|
12
12
|
UNMANAGED_START,
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
runManagedWritePreflight,
|
|
4
4
|
runManagedWriteTransaction
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-NOXDRIHM.js";
|
|
6
|
+
import "./chunk-5XQWYC5K.js";
|
|
7
|
+
import "./chunk-UI4SOWI2.js";
|
|
8
|
+
import "./chunk-SGZPIGGX.js";
|
|
9
|
+
import "./chunk-RQARJ6HB.js";
|
|
8
10
|
export {
|
|
9
11
|
runManagedWritePreflight,
|
|
10
12
|
runManagedWriteTransaction
|
package/dist/skillwiki-mcp.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runSkillwikiMcpStdio
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-W7NPTDIU.js";
|
|
5
5
|
import "./chunk-7I2TPIV5.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-OJVIND5D.js";
|
|
7
|
+
import "./chunk-5XQWYC5K.js";
|
|
8
|
+
import "./chunk-UI4SOWI2.js";
|
|
9
|
+
import "./chunk-SGZPIGGX.js";
|
|
10
|
+
import "./chunk-RQARJ6HB.js";
|
|
11
11
|
|
|
12
12
|
// src/mcp-entry.ts
|
|
13
13
|
runSkillwikiMcpStdio().catch((error) => {
|
|
@@ -147,6 +147,270 @@ platform_job_status() {
|
|
|
147
147
|
printf '{"enabled": %s, "running": %s, "last_exit": %d}\n' "$_enabled" "$_running" "$_last_exit"
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
# macOS LaunchAgent plist validation. Callers inspect the non-empty
|
|
151
|
+
# PLATFORM_LAUNCHD_PLIST_REASON when this returns non-zero. The generated
|
|
152
|
+
# vault-sync units are XML plists, but use plutil to canonicalize a valid
|
|
153
|
+
# binary plist before applying the same structural checks. The validated first
|
|
154
|
+
# argument is exported for installer-only executable preflight warnings.
|
|
155
|
+
PLATFORM_LAUNCHD_PLIST_REASON=""
|
|
156
|
+
PLATFORM_LAUNCHD_PLIST_PROGRAM=""
|
|
157
|
+
|
|
158
|
+
platform_launchd_plist_validate() {
|
|
159
|
+
_platform_expected_label="${1:-}"
|
|
160
|
+
_platform_plist="${2:-}"
|
|
161
|
+
_platform_xml=""
|
|
162
|
+
_platform_fields=""
|
|
163
|
+
_platform_document_kind=""
|
|
164
|
+
_platform_label_kind=""
|
|
165
|
+
_platform_program_kind=""
|
|
166
|
+
_platform_actual_label=""
|
|
167
|
+
_platform_program=""
|
|
168
|
+
PLATFORM_LAUNCHD_PLIST_REASON=""
|
|
169
|
+
PLATFORM_LAUNCHD_PLIST_PROGRAM=""
|
|
170
|
+
|
|
171
|
+
if [ -z "$_platform_expected_label" ]; then
|
|
172
|
+
PLATFORM_LAUNCHD_PLIST_REASON="expected Label is empty"
|
|
173
|
+
return 1
|
|
174
|
+
fi
|
|
175
|
+
if [ -z "$_platform_plist" ] || [ ! -f "$_platform_plist" ]; then
|
|
176
|
+
PLATFORM_LAUNCHD_PLIST_REASON="plist file missing: ${_platform_plist:-unknown}"
|
|
177
|
+
return 1
|
|
178
|
+
fi
|
|
179
|
+
if [ ! -r "$_platform_plist" ]; then
|
|
180
|
+
PLATFORM_LAUNCHD_PLIST_REASON="plist file is not readable: $_platform_plist"
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if command -v plutil >/dev/null 2>&1; then
|
|
185
|
+
if ! plutil -lint "$_platform_plist" >/dev/null 2>&1; then
|
|
186
|
+
PLATFORM_LAUNCHD_PLIST_REASON="plutil -lint failed"
|
|
187
|
+
return 1
|
|
188
|
+
fi
|
|
189
|
+
if ! _platform_xml="$(plutil -convert xml1 -o - "$_platform_plist" 2>/dev/null)"; then
|
|
190
|
+
PLATFORM_LAUNCHD_PLIST_REASON="plutil could not render plist as XML"
|
|
191
|
+
return 1
|
|
192
|
+
fi
|
|
193
|
+
else
|
|
194
|
+
# Keep status useful on non-macOS CI hosts that lack plutil. This fallback
|
|
195
|
+
# intentionally supports structural XML only; macOS uses plutil above for
|
|
196
|
+
# syntactic validation and binary-plist conversion.
|
|
197
|
+
_platform_xml="$(cat "$_platform_plist")"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Parse the root plist dictionary structurally rather than searching for
|
|
201
|
+
# text. In particular, a Label-like string inside ProgramArguments must not
|
|
202
|
+
# satisfy Label validation, and ProgramArguments[0] must actually be a
|
|
203
|
+
# non-empty <string>, not merely a scalar which plutil can render as raw.
|
|
204
|
+
_platform_fields="$(
|
|
205
|
+
printf '%s\n' "$_platform_xml" | awk '
|
|
206
|
+
BEGIN {
|
|
207
|
+
label_kind = "missing"
|
|
208
|
+
program_kind = "missing"
|
|
209
|
+
}
|
|
210
|
+
function trim(value) {
|
|
211
|
+
sub(/^[[:space:]]+/, "", value)
|
|
212
|
+
sub(/[[:space:]]+$/, "", value)
|
|
213
|
+
return value
|
|
214
|
+
}
|
|
215
|
+
function string_value(value) {
|
|
216
|
+
sub(/^[[:space:]]*<string>/, "", value)
|
|
217
|
+
sub(/<\/string>[[:space:]]*$/, "", value)
|
|
218
|
+
return value
|
|
219
|
+
}
|
|
220
|
+
function invalid() {
|
|
221
|
+
invalid_token = 1
|
|
222
|
+
}
|
|
223
|
+
function open_container(kind) {
|
|
224
|
+
if (!plist_seen || plist_closed) {
|
|
225
|
+
invalid()
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
if (container_depth == 0) {
|
|
229
|
+
if (kind != "dict" || root_dict_seen) {
|
|
230
|
+
invalid()
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
root_dict_seen = 1
|
|
234
|
+
}
|
|
235
|
+
container_depth++
|
|
236
|
+
container_stack[container_depth] = kind
|
|
237
|
+
if (kind == "dict") dict_depth++
|
|
238
|
+
}
|
|
239
|
+
function close_container(kind) {
|
|
240
|
+
if (container_depth == 0 || container_stack[container_depth] != kind) {
|
|
241
|
+
invalid()
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
if (kind == "dict") {
|
|
245
|
+
if (dict_depth == 1) root_dict_closed = 1
|
|
246
|
+
dict_depth--
|
|
247
|
+
}
|
|
248
|
+
delete container_stack[container_depth]
|
|
249
|
+
container_depth--
|
|
250
|
+
}
|
|
251
|
+
function process(line) {
|
|
252
|
+
line = trim(line)
|
|
253
|
+
if (line == "") return
|
|
254
|
+
if (line ~ /^<!--.*-->$/) return
|
|
255
|
+
if (line ~ /^<\?xml[[:space:]][^>]*\?>$/) {
|
|
256
|
+
if (plist_seen || xml_declaration_seen) invalid()
|
|
257
|
+
xml_declaration_seen = 1
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
if (line ~ /^<!DOCTYPE[[:space:]].*>$/) {
|
|
261
|
+
if (plist_seen || doctype_seen) invalid()
|
|
262
|
+
doctype_seen = 1
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
if (line ~ /^<plist([[:space:]][^>]*)?>$/) {
|
|
266
|
+
if (plist_seen || plist_closed || container_depth != 0 || root_dict_seen) {
|
|
267
|
+
invalid()
|
|
268
|
+
} else {
|
|
269
|
+
plist_seen = 1
|
|
270
|
+
}
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
if (line == "</plist>") {
|
|
274
|
+
if (!plist_seen || plist_closed || !root_dict_closed || container_depth != 0) {
|
|
275
|
+
invalid()
|
|
276
|
+
} else {
|
|
277
|
+
plist_closed = 1
|
|
278
|
+
}
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (label_wait) {
|
|
283
|
+
if (line ~ /^<string>[^<]*<\/string>$/) {
|
|
284
|
+
label_value = string_value(line)
|
|
285
|
+
label_kind = "string"
|
|
286
|
+
label_wait = 0
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
label_kind = "not-string"
|
|
290
|
+
label_wait = 0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (program_wait_array) {
|
|
294
|
+
if (line == "<array>") {
|
|
295
|
+
program_wait_array = 0
|
|
296
|
+
program_wait_first = 1
|
|
297
|
+
open_container("array")
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
program_kind = "not-array"
|
|
301
|
+
program_wait_array = 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (program_wait_first) {
|
|
305
|
+
if (line ~ /^<string>[^<]*<\/string>$/) {
|
|
306
|
+
program_value = string_value(line)
|
|
307
|
+
program_kind = (program_value == "" ? "empty" : "string")
|
|
308
|
+
program_wait_first = 0
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
program_kind = (line == "</array>" ? "missing" : "not-string")
|
|
312
|
+
program_wait_first = 0
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (line == "<dict>") {
|
|
316
|
+
open_container("dict")
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
if (line == "</dict>") {
|
|
320
|
+
close_container("dict")
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
if (line == "<array>") {
|
|
324
|
+
open_container("array")
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
if (line == "</array>") {
|
|
328
|
+
close_container("array")
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
if (line ~ /^<key>Label<\/key>$/ && dict_depth == 1 && container_depth == 1 && label_kind == "missing") {
|
|
332
|
+
label_wait = 1
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
if (line ~ /^<key>ProgramArguments<\/key>$/ && dict_depth == 1 && container_depth == 1 && program_kind == "missing") {
|
|
336
|
+
program_wait_array = 1
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
if (line ~ /^<key>[^<]*<\/key>$/ ||
|
|
340
|
+
line ~ /^<string>[^<]*<\/string>$/ ||
|
|
341
|
+
line ~ /^<integer>[^<]*<\/integer>$/ ||
|
|
342
|
+
line ~ /^<real>[^<]*<\/real>$/ ||
|
|
343
|
+
line ~ /^<date>[^<]*<\/date>$/ ||
|
|
344
|
+
line ~ /^<data>[^<]*<\/data>$/ ||
|
|
345
|
+
line == "<true/>" || line == "<false/>") {
|
|
346
|
+
if (!plist_seen || plist_closed || container_depth == 0) invalid()
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
invalid()
|
|
350
|
+
}
|
|
351
|
+
{
|
|
352
|
+
content = $0
|
|
353
|
+
gsub(/></, ">\n<", content)
|
|
354
|
+
count = split(content, chunks, "\n")
|
|
355
|
+
for (chunk_index = 1; chunk_index <= count; chunk_index++) process(chunks[chunk_index])
|
|
356
|
+
}
|
|
357
|
+
END {
|
|
358
|
+
if (label_wait && label_kind == "missing") label_kind = "not-string"
|
|
359
|
+
if (program_wait_array) program_kind = "not-array"
|
|
360
|
+
if (program_wait_first) program_kind = "missing"
|
|
361
|
+
document_kind = (plist_seen && plist_closed && root_dict_seen && root_dict_closed &&
|
|
362
|
+
dict_depth == 0 && container_depth == 0 && !invalid_token ? "valid" : "invalid")
|
|
363
|
+
printf "DOCUMENT_KIND=%s\n", document_kind
|
|
364
|
+
printf "LABEL_KIND=%s\n", label_kind
|
|
365
|
+
printf "LABEL_VALUE=%s\n", label_value
|
|
366
|
+
printf "PROGRAM_KIND=%s\n", program_kind
|
|
367
|
+
printf "PROGRAM_VALUE=%s\n", program_value
|
|
368
|
+
}
|
|
369
|
+
'
|
|
370
|
+
)"
|
|
371
|
+
_platform_document_kind="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^DOCUMENT_KIND=/, "") { print; exit }')"
|
|
372
|
+
_platform_label_kind="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^LABEL_KIND=/, "") { print; exit }')"
|
|
373
|
+
_platform_actual_label="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^LABEL_VALUE=/, "") { print; exit }')"
|
|
374
|
+
_platform_program_kind="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^PROGRAM_KIND=/, "") { print; exit }')"
|
|
375
|
+
_platform_program="$(printf '%s\n' "$_platform_fields" | awk 'sub(/^PROGRAM_VALUE=/, "") { print; exit }')"
|
|
376
|
+
|
|
377
|
+
if [ "$_platform_document_kind" != "valid" ]; then
|
|
378
|
+
PLATFORM_LAUNCHD_PLIST_REASON="plist XML structure is invalid"
|
|
379
|
+
return 1
|
|
380
|
+
fi
|
|
381
|
+
if [ "$_platform_label_kind" != "string" ]; then
|
|
382
|
+
PLATFORM_LAUNCHD_PLIST_REASON="Label missing or is not a string"
|
|
383
|
+
return 1
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
if [ "$_platform_actual_label" != "$_platform_expected_label" ]; then
|
|
387
|
+
PLATFORM_LAUNCHD_PLIST_REASON="Label is '${_platform_actual_label:-missing}', expected '$_platform_expected_label'"
|
|
388
|
+
return 1
|
|
389
|
+
fi
|
|
390
|
+
case "$_platform_program_kind" in
|
|
391
|
+
missing)
|
|
392
|
+
PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] missing"
|
|
393
|
+
return 1
|
|
394
|
+
;;
|
|
395
|
+
empty)
|
|
396
|
+
PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] is empty"
|
|
397
|
+
return 1
|
|
398
|
+
;;
|
|
399
|
+
string)
|
|
400
|
+
;;
|
|
401
|
+
*)
|
|
402
|
+
PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] must be a string"
|
|
403
|
+
return 1
|
|
404
|
+
;;
|
|
405
|
+
esac
|
|
406
|
+
if [ -z "$_platform_program" ]; then
|
|
407
|
+
PLATFORM_LAUNCHD_PLIST_REASON="ProgramArguments[0] missing"
|
|
408
|
+
return 1
|
|
409
|
+
fi
|
|
410
|
+
PLATFORM_LAUNCHD_PLIST_PROGRAM="$_platform_program"
|
|
411
|
+
return 0
|
|
412
|
+
}
|
|
413
|
+
|
|
150
414
|
# Feature prerequisite check:
|
|
151
415
|
# exit 1 with message if not available
|
|
152
416
|
platform_require() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.28",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 19 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|
package/skills/package.json
CHANGED
package/dist/chunk-6ZDKTNLA.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
err,
|
|
4
|
-
getErrorMessage,
|
|
5
|
-
ok
|
|
6
|
-
} from "./chunk-C2DKFJFA.js";
|
|
7
|
-
|
|
8
|
-
// src/parsers/frontmatter.ts
|
|
9
|
-
import yaml from "js-yaml";
|
|
10
|
-
var FM_OPEN = /^---\r?\n/;
|
|
11
|
-
function splitFrontmatter(text) {
|
|
12
|
-
if (!FM_OPEN.test(text)) return ok({ rawFrontmatter: "", body: text, bodyStart: 0 });
|
|
13
|
-
const afterOpen = text.replace(FM_OPEN, "");
|
|
14
|
-
const closeIdx = afterOpen.search(/\r?\n---\r?\n/);
|
|
15
|
-
if (closeIdx === -1) return err("MISSING_CLOSING_DELIMITER");
|
|
16
|
-
const rawFrontmatter = afterOpen.slice(0, closeIdx);
|
|
17
|
-
const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\r?\n/);
|
|
18
|
-
const bodyStart = text.length - (afterOpen.length - closeIdx - closeMatch[0].length);
|
|
19
|
-
const body = text.slice(bodyStart);
|
|
20
|
-
return ok({ rawFrontmatter, body, bodyStart });
|
|
21
|
-
}
|
|
22
|
-
function extractFrontmatter(text) {
|
|
23
|
-
const split = splitFrontmatter(text);
|
|
24
|
-
if (!split.ok) return split;
|
|
25
|
-
if (!split.data.rawFrontmatter) return ok({});
|
|
26
|
-
try {
|
|
27
|
-
const parsed = yaml.load(split.data.rawFrontmatter, { schema: yaml.JSON_SCHEMA });
|
|
28
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return ok({});
|
|
29
|
-
return ok(parsed);
|
|
30
|
-
} catch (e) {
|
|
31
|
-
return err("INVALID_FRONTMATTER", { message: getErrorMessage(e) });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/utils/sensitive-content.ts
|
|
36
|
-
import { createHash } from "crypto";
|
|
37
|
-
var REDACTED_RE = /\[REDACTED:[^\]]+\]/i;
|
|
38
|
-
var SYNTHETIC_RE = /^(?:<[^>]+>|\$\{[^}]+\}|REPLACE_WITH_[A-Z0-9_]+|YOUR_[A-Z0-9_]+|EXAMPLE_[A-Z0-9_]+)$/i;
|
|
39
|
-
var MATCHERS = [
|
|
40
|
-
{
|
|
41
|
-
kind: "private_key",
|
|
42
|
-
re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
kind: "authorization_header",
|
|
46
|
-
re: /\bAuthorization["']?\s*:\s*["']?(Bearer\s+[A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
47
|
-
valueGroup: 1
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
kind: "cookie",
|
|
51
|
-
re: /\b(?:Cookie|Set-Cookie)\s*:\s*([^\n]{20,})/gi,
|
|
52
|
-
valueGroup: 1
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
kind: "jwt",
|
|
56
|
-
re: /\b([A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,})\b/g,
|
|
57
|
-
valueGroup: 1
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
kind: "provider_key",
|
|
61
|
-
re: /\b(sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|gh[pousr]_[A-Za-z0-9_=-]{20,})\b/g,
|
|
62
|
-
valueGroup: 1
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
kind: "access_key",
|
|
66
|
-
re: /\b((?:AKIA|ASIA)[A-Z0-9]{16})\b/g,
|
|
67
|
-
valueGroup: 1
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
kind: "access_key",
|
|
71
|
-
re: /\b(?:access[-_ ]?key|credential)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
72
|
-
valueGroup: 1
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
kind: "api_key",
|
|
76
|
-
re: /\b(?:api[-_ ]?key)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
77
|
-
valueGroup: 1
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
kind: "password",
|
|
81
|
-
re: /\b(?:pass(?:word|wd)?)["']?\s*[:=]\s*["']?([^\s`"']{8,})["']?/gi,
|
|
82
|
-
valueGroup: 1
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
kind: "secret",
|
|
86
|
-
re: /\b(?:secret|client[-_ ]?secret)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
87
|
-
valueGroup: 1
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
kind: "token",
|
|
91
|
-
re: /\b(?:token|session)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
92
|
-
valueGroup: 1
|
|
93
|
-
}
|
|
94
|
-
];
|
|
95
|
-
function fingerprint(value) {
|
|
96
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
97
|
-
}
|
|
98
|
-
function lineFor(text, offset) {
|
|
99
|
-
return text.slice(0, offset).split(/\r?\n/).length;
|
|
100
|
-
}
|
|
101
|
-
function redactMarker(kind, value) {
|
|
102
|
-
return `[REDACTED:${kind}:${fingerprint(value)}]`;
|
|
103
|
-
}
|
|
104
|
-
function isSyntheticPlaceholder(value) {
|
|
105
|
-
return REDACTED_RE.test(value) || SYNTHETIC_RE.test(value.trim());
|
|
106
|
-
}
|
|
107
|
-
function isNonSecretTokenCapture(value) {
|
|
108
|
-
if (value.startsWith("//")) return true;
|
|
109
|
-
if (/^[a-z]{2,}(?:-[a-z]{2,})+$/.test(value)) return true;
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
function collectMatches(text) {
|
|
113
|
-
const matches = [];
|
|
114
|
-
for (const matcher of MATCHERS) {
|
|
115
|
-
matcher.re.lastIndex = 0;
|
|
116
|
-
for (const m of text.matchAll(matcher.re)) {
|
|
117
|
-
const whole = m[0];
|
|
118
|
-
const start = m.index ?? 0;
|
|
119
|
-
if (REDACTED_RE.test(whole)) continue;
|
|
120
|
-
const value = matcher.valueGroup ? m[matcher.valueGroup] : whole;
|
|
121
|
-
if (isSyntheticPlaceholder(value)) continue;
|
|
122
|
-
if (matcher.kind === "token" && isNonSecretTokenCapture(value)) continue;
|
|
123
|
-
const valueOffset = whole.lastIndexOf(value);
|
|
124
|
-
const valueStart = start + Math.max(0, valueOffset);
|
|
125
|
-
matches.push({
|
|
126
|
-
start,
|
|
127
|
-
end: start + whole.length,
|
|
128
|
-
valueStart,
|
|
129
|
-
valueEnd: valueStart + value.length,
|
|
130
|
-
kind: matcher.kind
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return matches.sort((a, b) => {
|
|
135
|
-
if (a.valueStart !== b.valueStart) return a.valueStart - b.valueStart;
|
|
136
|
-
return b.valueEnd - b.valueStart - (a.valueEnd - a.valueStart);
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
function collapseOverlaps(matches) {
|
|
140
|
-
const kept = [];
|
|
141
|
-
for (const match of matches) {
|
|
142
|
-
const overlaps = kept.some((k) => match.valueStart < k.valueEnd && match.valueEnd > k.valueStart);
|
|
143
|
-
if (!overlaps) kept.push(match);
|
|
144
|
-
}
|
|
145
|
-
return kept;
|
|
146
|
-
}
|
|
147
|
-
function scanSensitiveContent(text, opts = {}) {
|
|
148
|
-
return collapseOverlaps(collectMatches(text)).map((match) => {
|
|
149
|
-
const value = text.slice(match.valueStart, match.valueEnd);
|
|
150
|
-
const marker = redactMarker(match.kind, value);
|
|
151
|
-
const rawPreview = text.slice(Math.max(0, match.start - 24), Math.min(text.length, match.end + 24));
|
|
152
|
-
const preview = rawPreview.replace(value, marker);
|
|
153
|
-
return {
|
|
154
|
-
file: opts.file,
|
|
155
|
-
line: lineFor(text, match.valueStart),
|
|
156
|
-
kind: match.kind,
|
|
157
|
-
preview,
|
|
158
|
-
fingerprint: fingerprint(value)
|
|
159
|
-
};
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function redactSensitiveContent(text, opts = {}) {
|
|
163
|
-
const matches = collapseOverlaps(collectMatches(text));
|
|
164
|
-
if (matches.length === 0) return { text, changed: false, findings: [] };
|
|
165
|
-
let out = "";
|
|
166
|
-
let cursor = 0;
|
|
167
|
-
for (const match of matches) {
|
|
168
|
-
const value = text.slice(match.valueStart, match.valueEnd);
|
|
169
|
-
out += text.slice(cursor, match.valueStart);
|
|
170
|
-
out += redactMarker(match.kind, value);
|
|
171
|
-
cursor = match.valueEnd;
|
|
172
|
-
}
|
|
173
|
-
out += text.slice(cursor);
|
|
174
|
-
return {
|
|
175
|
-
text: out,
|
|
176
|
-
changed: out !== text,
|
|
177
|
-
findings: scanSensitiveContent(text, opts)
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// src/utils/vault.ts
|
|
182
|
-
import { existsSync, readFileSync } from "fs";
|
|
183
|
-
import { readFile, readdir, stat } from "fs/promises";
|
|
184
|
-
import { join, relative, sep } from "path";
|
|
185
|
-
var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
186
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
187
|
-
var DEFAULT_IO_CONCURRENCY = 1;
|
|
188
|
-
function vaultIoConcurrency() {
|
|
189
|
-
const raw = Number.parseInt(process.env.SKILLWIKI_VAULT_IO_CONCURRENCY ?? "", 10);
|
|
190
|
-
return Number.isFinite(raw) && raw > 0 ? Math.min(raw, 64) : DEFAULT_IO_CONCURRENCY;
|
|
191
|
-
}
|
|
192
|
-
function decodeProcMountPath(value) {
|
|
193
|
-
return value.replace(/\\040/g, " ");
|
|
194
|
-
}
|
|
195
|
-
function isRcloneFuseVaultFromMounts(root, mounts) {
|
|
196
|
-
return mounts.split(/\r?\n/).some((line) => {
|
|
197
|
-
const parts = line.split(" ");
|
|
198
|
-
if (parts.length < 3) return false;
|
|
199
|
-
const mountPoint = decodeProcMountPath(parts[1]);
|
|
200
|
-
const fsType = parts[2];
|
|
201
|
-
return fsType === "fuse.rclone" && (root === mountPoint || root.startsWith(`${mountPoint}/`));
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
function resolveReadOnlyVaultRootWithMounts(root, mounts) {
|
|
205
|
-
if (/^(1|true|yes)$/i.test(process.env.SKILLWIKI_DISABLE_VAULT_READ_MIRROR ?? "")) {
|
|
206
|
-
return { root, mirrored: false };
|
|
207
|
-
}
|
|
208
|
-
const explicitMirror = process.env.SKILLWIKI_VAULT_READ_MIRROR;
|
|
209
|
-
if (explicitMirror && existsSync(join(explicitMirror, "SCHEMA.md"))) {
|
|
210
|
-
return { root: explicitMirror, mirrored: explicitMirror !== root };
|
|
211
|
-
}
|
|
212
|
-
const siblingMirror = `${root}-git`;
|
|
213
|
-
if (isRcloneFuseVaultFromMounts(root, mounts) && existsSync(join(siblingMirror, "SCHEMA.md"))) {
|
|
214
|
-
return { root: siblingMirror, mirrored: true };
|
|
215
|
-
}
|
|
216
|
-
return { root, mirrored: false };
|
|
217
|
-
}
|
|
218
|
-
function resolveReadOnlyVaultRoot(root) {
|
|
219
|
-
let mounts = "";
|
|
220
|
-
try {
|
|
221
|
-
mounts = readFileSync("/proc/mounts", "utf8");
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
224
|
-
return resolveReadOnlyVaultRootWithMounts(root, mounts);
|
|
225
|
-
}
|
|
226
|
-
async function mapWithConcurrency(items, limit, mapper) {
|
|
227
|
-
const out = new Array(items.length);
|
|
228
|
-
let next = 0;
|
|
229
|
-
const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, async () => {
|
|
230
|
-
for (; ; ) {
|
|
231
|
-
const index = next++;
|
|
232
|
-
if (index >= items.length) return;
|
|
233
|
-
out[index] = await mapper(items[index], index);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
await Promise.all(workers);
|
|
237
|
-
return out;
|
|
238
|
-
}
|
|
239
|
-
async function scanVault(root) {
|
|
240
|
-
try {
|
|
241
|
-
await stat(join(root, "SCHEMA.md"));
|
|
242
|
-
} catch {
|
|
243
|
-
return err("VAULT_PATH_INVALID", { root, reason: "SCHEMA.md missing" });
|
|
244
|
-
}
|
|
245
|
-
const all = await walk(root);
|
|
246
|
-
const rels = all.map((p) => ({ absPath: p, relPath: relative(root, p).split(sep).join("/") }));
|
|
247
|
-
return ok({
|
|
248
|
-
root,
|
|
249
|
-
allMarkdown: rels,
|
|
250
|
-
typedKnowledge: rels.filter((p) => TYPED_DIRS.some((d) => p.relPath.startsWith(d + "/"))),
|
|
251
|
-
raw: rels.filter((p) => p.relPath.startsWith("raw/")),
|
|
252
|
-
workItems: rels.filter((p) => /^projects\/[^/]+\/work\/[^/]+\/(spec|plan|log)\.md$/.test(p.relPath)),
|
|
253
|
-
compound: rels.filter((p) => /^projects\/[^/]+\/compound\//.test(p.relPath))
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
async function walk(dir) {
|
|
257
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
258
|
-
const out = [];
|
|
259
|
-
const subdirs = [];
|
|
260
|
-
for (const e of entries) {
|
|
261
|
-
const p = join(dir, e.name);
|
|
262
|
-
if (e.isDirectory()) {
|
|
263
|
-
if (SKIP_DIRS.has(e.name)) continue;
|
|
264
|
-
subdirs.push(p);
|
|
265
|
-
} else if (e.isFile() && e.name.endsWith(".md")) out.push(p);
|
|
266
|
-
}
|
|
267
|
-
const nested = await mapWithConcurrency(subdirs, Math.min(8, vaultIoConcurrency()), walk);
|
|
268
|
-
for (const files of nested) out.push(...files);
|
|
269
|
-
return out;
|
|
270
|
-
}
|
|
271
|
-
async function readPage(p) {
|
|
272
|
-
return readFile(p.absPath, "utf8");
|
|
273
|
-
}
|
|
274
|
-
async function readPageCached(p, cache) {
|
|
275
|
-
if (!cache) return readPage(p);
|
|
276
|
-
const existing = cache.get(p.absPath);
|
|
277
|
-
if (existing) return existing;
|
|
278
|
-
const pending = readPage(p);
|
|
279
|
-
cache.set(p.absPath, pending);
|
|
280
|
-
return pending;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export {
|
|
284
|
-
splitFrontmatter,
|
|
285
|
-
extractFrontmatter,
|
|
286
|
-
scanSensitiveContent,
|
|
287
|
-
redactSensitiveContent,
|
|
288
|
-
vaultIoConcurrency,
|
|
289
|
-
resolveReadOnlyVaultRoot,
|
|
290
|
-
mapWithConcurrency,
|
|
291
|
-
scanVault,
|
|
292
|
-
readPage,
|
|
293
|
-
readPageCached
|
|
294
|
-
};
|