skillwiki 0.10.49 → 0.10.51

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.
@@ -8,7 +8,7 @@ import {
8
8
  ok,
9
9
  scanSensitiveContent,
10
10
  scanVault
11
- } from "./chunk-EVQASYII.js";
11
+ } from "./chunk-IJ7DD7QZ.js";
12
12
 
13
13
  // src/utils/atomic-write.ts
14
14
  import { randomBytes } from "crypto";
@@ -6,7 +6,102 @@ import {
6
6
  readPage,
7
7
  scanSensitiveContent,
8
8
  splitFrontmatter
9
- } from "./chunk-EVQASYII.js";
9
+ } from "./chunk-IJ7DD7QZ.js";
10
+
11
+ // src/utils/vault-path-safety.ts
12
+ import { lstatSync, realpathSync } from "fs";
13
+ import { lstat, realpath } from "fs/promises";
14
+ import { dirname, relative, resolve, sep } from "path";
15
+ function vaultRelative(vaultReal, candidate) {
16
+ return relative(vaultReal, candidate).split(sep).join("/");
17
+ }
18
+ function isInside(vaultReal, candidate) {
19
+ const rel = vaultRelative(vaultReal, candidate);
20
+ return rel === "" || rel !== ".." && !rel.startsWith("../");
21
+ }
22
+ function existingRegularFileInsideVaultSync(vault, target) {
23
+ try {
24
+ const vaultReal = realpathSync(vault);
25
+ const absolutePath = resolve(vaultReal, target);
26
+ const rel = vaultRelative(vaultReal, absolutePath);
27
+ if (!isInside(vaultReal, absolutePath) || rel === "") return false;
28
+ let current = vaultReal;
29
+ for (const part of rel.split("/").filter(Boolean)) {
30
+ current = resolve(current, part);
31
+ if (lstatSync(current).isSymbolicLink()) return false;
32
+ }
33
+ const info = lstatSync(absolutePath);
34
+ return info.isFile() && !info.isSymbolicLink() && realpathSync(absolutePath) === absolutePath;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ async function canonicalVault(vault, target) {
40
+ try {
41
+ return ok(await realpath(vault));
42
+ } catch (error) {
43
+ return err("VAULT_PATH_INVALID", { target, message: "vault realpath failed", cause: String(error) });
44
+ }
45
+ }
46
+ async function rejectSymlinkedComponents(vaultReal, absolutePath, target) {
47
+ const rel = vaultRelative(vaultReal, absolutePath);
48
+ if (!isInside(vaultReal, absolutePath) || rel === "") {
49
+ return err("VAULT_PATH_INVALID", { target, message: "target escapes vault" });
50
+ }
51
+ const parts = rel.split("/").filter(Boolean);
52
+ let current = vaultReal;
53
+ for (const part of parts) {
54
+ current = resolve(current, part);
55
+ try {
56
+ const info = await lstat(current);
57
+ if (info.isSymbolicLink()) {
58
+ return err("VAULT_PATH_INVALID", { target, message: "target path may not contain symlink aliases" });
59
+ }
60
+ } catch (error) {
61
+ if (error.code === "ENOENT") break;
62
+ return err("VAULT_PATH_INVALID", { target, message: "target lstat failed", cause: String(error) });
63
+ }
64
+ }
65
+ return ok(true);
66
+ }
67
+ async function resolveExistingRegularFileInsideVault(vault, target) {
68
+ const vaultResult = await canonicalVault(vault, target);
69
+ if (!vaultResult.ok) return vaultResult;
70
+ const vaultReal = vaultResult.data;
71
+ const absolutePath = resolve(vaultReal, target);
72
+ const components = await rejectSymlinkedComponents(vaultReal, absolutePath, target);
73
+ if (!components.ok) return components;
74
+ try {
75
+ const info = await lstat(absolutePath);
76
+ if (!info.isFile() || info.isSymbolicLink()) {
77
+ return err("VAULT_PATH_INVALID", { target, message: "target must be a regular non-symlink file" });
78
+ }
79
+ const targetReal = await realpath(absolutePath);
80
+ if (!isInside(vaultReal, targetReal) || targetReal !== absolutePath) {
81
+ return err("VAULT_PATH_INVALID", { target, message: "target realpath escapes vault or uses an alias" });
82
+ }
83
+ return ok(absolutePath);
84
+ } catch (error) {
85
+ return err("FILE_NOT_FOUND", { path: target, message: String(error) });
86
+ }
87
+ }
88
+ async function resolveAbsentTargetInsideVault(vault, target) {
89
+ const vaultResult = await canonicalVault(vault, target);
90
+ if (!vaultResult.ok) return vaultResult;
91
+ const vaultReal = vaultResult.data;
92
+ const absolutePath = resolve(vaultReal, target);
93
+ const components = await rejectSymlinkedComponents(vaultReal, dirname(absolutePath), target);
94
+ if (!components.ok) return components;
95
+ try {
96
+ await lstat(absolutePath);
97
+ return err("RAW_DESTINATION_EXISTS", { path: target });
98
+ } catch (error) {
99
+ if (error.code !== "ENOENT") {
100
+ return err("VAULT_PATH_INVALID", { target, message: "destination lstat failed", cause: String(error) });
101
+ }
102
+ }
103
+ return ok(absolutePath);
104
+ }
10
105
 
11
106
  // src/utils/log-events.ts
12
107
  import { mkdir, open, readFile, readdir } from "fs/promises";
@@ -252,6 +347,74 @@ function hasWikilinkCitations(body) {
252
347
  return /\[\[raw\/[^\]]+\]\]/.test(stripped);
253
348
  }
254
349
 
350
+ // src/utils/raw-operation-policy.ts
351
+ import { posix } from "path";
352
+ var SOURCE_CATEGORIES = /* @__PURE__ */ new Set(["articles", "papers", "transcripts"]);
353
+ function classifyRawPath(value) {
354
+ const path = value.replaceAll("\\", "/");
355
+ if (path !== posix.normalize(path) || path.startsWith("/") || path.includes("\0") || path.split("/").includes("..")) {
356
+ return err("RAW_PATH_INVALID", { path: value });
357
+ }
358
+ const parts = path.split("/");
359
+ if (parts[0] !== "raw") return err("RAW_PATH_OUTSIDE_LAYER", { path });
360
+ if (parts[1] === "assets" && parts.length > 2) {
361
+ return ok({ path, category: "assets", storage: "asset", relativeWithinCategory: parts.slice(2).join("/") });
362
+ }
363
+ if ((parts[1] === "archived" || parts[1] === "duplicates") && SOURCE_CATEGORIES.has(parts[2] ?? "") && parts.length > 3) {
364
+ return ok({
365
+ path,
366
+ category: parts[2],
367
+ storage: parts[1] === "archived" ? "archived" : "duplicate",
368
+ relativeWithinCategory: parts.slice(3).join("/")
369
+ });
370
+ }
371
+ if (SOURCE_CATEGORIES.has(parts[1] ?? "") && parts.length > 2) {
372
+ return ok({
373
+ path,
374
+ category: parts[1],
375
+ storage: "active",
376
+ relativeWithinCategory: parts.slice(2).join("/")
377
+ });
378
+ }
379
+ return err("RAW_PATH_UNSUPPORTED", { path });
380
+ }
381
+ function lifecycleDestination(source, operation) {
382
+ const parsed = classifyRawPath(source);
383
+ if (!parsed.ok) return parsed;
384
+ if (parsed.data.category === "assets") {
385
+ return err("RAW_ASSET_PATH_FROZEN", { path: source, message: "asset lifecycle is logical by default" });
386
+ }
387
+ if (parsed.data.storage !== "active") return err("RAW_SOURCE_NOT_ACTIVE", { path: source, storage: parsed.data.storage });
388
+ if (operation === "archive") return ok(`raw/archived/${parsed.data.category}/${parsed.data.relativeWithinCategory}`);
389
+ if (operation === "deduplicate") return ok(`raw/duplicates/${parsed.data.category}/${parsed.data.relativeWithinCategory}`);
390
+ return ok(source);
391
+ }
392
+ function authorizeRawOperation(input) {
393
+ if (input.operationClass === "read") return ok(true);
394
+ if (input.operationClass === "rewrite") return err("RAW_REWRITE_FORBIDDEN", { source: input.source });
395
+ if (input.operationClass === "destructive-remove") {
396
+ return input.trigger === "attended-apply" && input.explicitExactTarget === true ? ok(true) : err("RAW_DISPOSAL_REQUIRES_EXACT_TARGET_APPROVAL", { source: input.source });
397
+ }
398
+ if (input.operationClass === "preserve-move") {
399
+ if (input.trigger === "report-only") return err("RAW_STRUCTURAL_REPORT_ONLY", { source: input.source, destination: input.destination });
400
+ if (!input.source || !input.destination) return err("RAW_PATH_INVALID", { source: input.source, destination: input.destination });
401
+ const source = classifyRawPath(input.source);
402
+ if (!source.ok) return source;
403
+ const destination = classifyRawPath(input.destination);
404
+ if (!destination.ok) return destination;
405
+ return ok(true);
406
+ }
407
+ return ok(true);
408
+ }
409
+
410
+ // src/utils/operation-id.ts
411
+ import { createHash } from "crypto";
412
+ function operationId(namespace, parts) {
413
+ const hash = createHash("sha256").update(namespace).update("\0");
414
+ for (const part of parts) hash.update(part).update("\0");
415
+ return hash.digest("hex");
416
+ }
417
+
255
418
  // src/utils/source-relocations.ts
256
419
  function parse(event) {
257
420
  if (event.kind !== "source-relocation") return ok(null);
@@ -311,109 +474,12 @@ function resolveRelocatedSource(target, projection) {
311
474
  }
312
475
 
313
476
  // src/utils/raw-source.ts
314
- import { join as join2, posix, relative as relative2 } from "path";
315
-
316
- // src/utils/vault-path-safety.ts
317
- import { lstatSync, realpathSync } from "fs";
318
- import { lstat, realpath } from "fs/promises";
319
- import { dirname, relative, resolve, sep } from "path";
320
- function vaultRelative(vaultReal, candidate) {
321
- return relative(vaultReal, candidate).split(sep).join("/");
322
- }
323
- function isInside(vaultReal, candidate) {
324
- const rel = vaultRelative(vaultReal, candidate);
325
- return rel === "" || rel !== ".." && !rel.startsWith("../");
326
- }
327
- function existingRegularFileInsideVaultSync(vault, target) {
328
- try {
329
- const vaultReal = realpathSync(vault);
330
- const absolutePath = resolve(vaultReal, target);
331
- const rel = vaultRelative(vaultReal, absolutePath);
332
- if (!isInside(vaultReal, absolutePath) || rel === "") return false;
333
- let current = vaultReal;
334
- for (const part of rel.split("/").filter(Boolean)) {
335
- current = resolve(current, part);
336
- if (lstatSync(current).isSymbolicLink()) return false;
337
- }
338
- const info = lstatSync(absolutePath);
339
- return info.isFile() && !info.isSymbolicLink() && realpathSync(absolutePath) === absolutePath;
340
- } catch {
341
- return false;
342
- }
343
- }
344
- async function canonicalVault(vault, target) {
345
- try {
346
- return ok(await realpath(vault));
347
- } catch (error) {
348
- return err("VAULT_PATH_INVALID", { target, message: "vault realpath failed", cause: String(error) });
349
- }
350
- }
351
- async function rejectSymlinkedComponents(vaultReal, absolutePath, target) {
352
- const rel = vaultRelative(vaultReal, absolutePath);
353
- if (!isInside(vaultReal, absolutePath) || rel === "") {
354
- return err("VAULT_PATH_INVALID", { target, message: "target escapes vault" });
355
- }
356
- const parts = rel.split("/").filter(Boolean);
357
- let current = vaultReal;
358
- for (const part of parts) {
359
- current = resolve(current, part);
360
- try {
361
- const info = await lstat(current);
362
- if (info.isSymbolicLink()) {
363
- return err("VAULT_PATH_INVALID", { target, message: "target path may not contain symlink aliases" });
364
- }
365
- } catch (error) {
366
- if (error.code === "ENOENT") break;
367
- return err("VAULT_PATH_INVALID", { target, message: "target lstat failed", cause: String(error) });
368
- }
369
- }
370
- return ok(true);
371
- }
372
- async function resolveExistingRegularFileInsideVault(vault, target) {
373
- const vaultResult = await canonicalVault(vault, target);
374
- if (!vaultResult.ok) return vaultResult;
375
- const vaultReal = vaultResult.data;
376
- const absolutePath = resolve(vaultReal, target);
377
- const components = await rejectSymlinkedComponents(vaultReal, absolutePath, target);
378
- if (!components.ok) return components;
379
- try {
380
- const info = await lstat(absolutePath);
381
- if (!info.isFile() || info.isSymbolicLink()) {
382
- return err("VAULT_PATH_INVALID", { target, message: "target must be a regular non-symlink file" });
383
- }
384
- const targetReal = await realpath(absolutePath);
385
- if (!isInside(vaultReal, targetReal) || targetReal !== absolutePath) {
386
- return err("VAULT_PATH_INVALID", { target, message: "target realpath escapes vault or uses an alias" });
387
- }
388
- return ok(absolutePath);
389
- } catch (error) {
390
- return err("FILE_NOT_FOUND", { path: target, message: String(error) });
391
- }
392
- }
393
- async function resolveAbsentTargetInsideVault(vault, target) {
394
- const vaultResult = await canonicalVault(vault, target);
395
- if (!vaultResult.ok) return vaultResult;
396
- const vaultReal = vaultResult.data;
397
- const absolutePath = resolve(vaultReal, target);
398
- const components = await rejectSymlinkedComponents(vaultReal, dirname(absolutePath), target);
399
- if (!components.ok) return components;
400
- try {
401
- await lstat(absolutePath);
402
- return err("RAW_DESTINATION_EXISTS", { path: target });
403
- } catch (error) {
404
- if (error.code !== "ENOENT") {
405
- return err("VAULT_PATH_INVALID", { target, message: "destination lstat failed", cause: String(error) });
406
- }
407
- }
408
- return ok(absolutePath);
409
- }
410
-
411
- // src/utils/raw-source.ts
477
+ import { join as join2, posix as posix2, relative as relative2 } from "path";
412
478
  function normalizeRawSourceTarget(entry) {
413
479
  let target = entry.trim().replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
414
480
  target = target.replace(/^\^\[/, "").replace(/\]$/, "");
415
- if (!target || target.includes("\0") || target.includes("\\") || posix.isAbsolute(target)) return null;
416
- if (posix.normalize(target) !== target || target.split("/").some((part) => part === "" || part === "." || part === "..")) return null;
481
+ if (!target || target.includes("\0") || target.includes("\\") || posix2.isAbsolute(target)) return null;
482
+ if (posix2.normalize(target) !== target || target.split("/").some((part) => part === "" || part === "." || part === "..")) return null;
417
483
  if (!target.startsWith("raw/") && !target.startsWith("_archive/raw/")) return null;
418
484
  return target;
419
485
  }
@@ -457,74 +523,6 @@ async function rawSourceTargetExists(vault, target) {
457
523
  return false;
458
524
  }
459
525
 
460
- // src/utils/raw-operation-policy.ts
461
- import { posix as posix2 } from "path";
462
- var SOURCE_CATEGORIES = /* @__PURE__ */ new Set(["articles", "papers", "transcripts"]);
463
- function classifyRawPath(value) {
464
- const path = value.replaceAll("\\", "/");
465
- if (path !== posix2.normalize(path) || path.startsWith("/") || path.includes("\0") || path.split("/").includes("..")) {
466
- return err("RAW_PATH_INVALID", { path: value });
467
- }
468
- const parts = path.split("/");
469
- if (parts[0] !== "raw") return err("RAW_PATH_OUTSIDE_LAYER", { path });
470
- if (parts[1] === "assets" && parts.length > 2) {
471
- return ok({ path, category: "assets", storage: "asset", relativeWithinCategory: parts.slice(2).join("/") });
472
- }
473
- if ((parts[1] === "archived" || parts[1] === "duplicates") && SOURCE_CATEGORIES.has(parts[2] ?? "") && parts.length > 3) {
474
- return ok({
475
- path,
476
- category: parts[2],
477
- storage: parts[1] === "archived" ? "archived" : "duplicate",
478
- relativeWithinCategory: parts.slice(3).join("/")
479
- });
480
- }
481
- if (SOURCE_CATEGORIES.has(parts[1] ?? "") && parts.length > 2) {
482
- return ok({
483
- path,
484
- category: parts[1],
485
- storage: "active",
486
- relativeWithinCategory: parts.slice(2).join("/")
487
- });
488
- }
489
- return err("RAW_PATH_UNSUPPORTED", { path });
490
- }
491
- function lifecycleDestination(source, operation) {
492
- const parsed = classifyRawPath(source);
493
- if (!parsed.ok) return parsed;
494
- if (parsed.data.category === "assets") {
495
- return err("RAW_ASSET_PATH_FROZEN", { path: source, message: "asset lifecycle is logical by default" });
496
- }
497
- if (parsed.data.storage !== "active") return err("RAW_SOURCE_NOT_ACTIVE", { path: source, storage: parsed.data.storage });
498
- if (operation === "archive") return ok(`raw/archived/${parsed.data.category}/${parsed.data.relativeWithinCategory}`);
499
- if (operation === "deduplicate") return ok(`raw/duplicates/${parsed.data.category}/${parsed.data.relativeWithinCategory}`);
500
- return ok(source);
501
- }
502
- function authorizeRawOperation(input) {
503
- if (input.operationClass === "read") return ok(true);
504
- if (input.operationClass === "rewrite") return err("RAW_REWRITE_FORBIDDEN", { source: input.source });
505
- if (input.operationClass === "destructive-remove") {
506
- return input.trigger === "attended-apply" && input.explicitExactTarget === true ? ok(true) : err("RAW_DISPOSAL_REQUIRES_EXACT_TARGET_APPROVAL", { source: input.source });
507
- }
508
- if (input.operationClass === "preserve-move") {
509
- if (input.trigger === "report-only") return err("RAW_STRUCTURAL_REPORT_ONLY", { source: input.source, destination: input.destination });
510
- if (!input.source || !input.destination) return err("RAW_PATH_INVALID", { source: input.source, destination: input.destination });
511
- const source = classifyRawPath(input.source);
512
- if (!source.ok) return source;
513
- const destination = classifyRawPath(input.destination);
514
- if (!destination.ok) return destination;
515
- return ok(true);
516
- }
517
- return ok(true);
518
- }
519
-
520
- // src/utils/operation-id.ts
521
- import { createHash } from "crypto";
522
- function operationId(namespace, parts) {
523
- const hash = createHash("sha256").update(namespace).update("\0");
524
- for (const part of parts) hash.update(part).update("\0");
525
- return hash.digest("hex");
526
- }
527
-
528
526
  // src/utils/source-reference-index.ts
529
527
  function canonicalTarget(value) {
530
528
  const normalized = normalizeRawSourceTarget(value);
@@ -598,17 +596,18 @@ export {
598
596
  isLegacyCitationStyle,
599
597
  hasOrphanedCitations,
600
598
  hasWikilinkCitations,
599
+ classifyRawPath,
600
+ lifecycleDestination,
601
+ authorizeRawOperation,
602
+ operationId,
603
+ resolveExistingRegularFileInsideVault,
604
+ resolveAbsentTargetInsideVault,
601
605
  projectSourceRelocations,
602
606
  readSourceRelocations,
603
607
  buildSourceRelocationProjection,
604
- resolveExistingRegularFileInsideVault,
605
- resolveAbsentTargetInsideVault,
606
608
  normalizeRawSourceTarget,
607
609
  rawSourceTargetExistsSync,
608
610
  rawSourceTargetExists,
609
- classifyRawPath,
610
- lifecycleDestination,
611
- authorizeRawOperation,
612
- operationId,
611
+ referencesFromText,
613
612
  buildSourceReferenceIndex
614
613
  };