skillwiki 0.10.22 → 0.10.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/chunk-6ZDKTNLA.js +294 -0
  2. package/dist/chunk-EQPU2BPM.js +468 -0
  3. package/dist/{chunk-54JVBNWK.js → chunk-QBZYEEBD.js} +12 -295
  4. package/dist/chunk-QNTBNNEL.js +616 -0
  5. package/dist/{chunk-7YDWC6D3.js → chunk-SYKSL3JQ.js} +852 -699
  6. package/dist/cli.js +613 -146
  7. package/dist/{index-projection-Q6WL76SO.js → index-projection-FRWTZB5Y.js} +2 -1
  8. package/dist/skillwiki-mcp.js +4 -2
  9. package/dist/sources-L2SQV63E.js +10 -0
  10. package/package.json +1 -1
  11. package/skills/.claude-plugin/plugin.json +1 -1
  12. package/skills/.codex-plugin/plugin.json +1 -1
  13. package/skills/README.md +13 -2
  14. package/skills/agents/wiki-add-task.md +2 -1
  15. package/skills/agents/wiki-archive.md +8 -9
  16. package/skills/agents/wiki-audit.md +2 -1
  17. package/skills/agents/wiki-lint.md +2 -1
  18. package/skills/agents/wiki-query.md +3 -1
  19. package/skills/agents/wiki-reingest.md +9 -8
  20. package/skills/package.json +1 -1
  21. package/skills/skills/using-skillwiki/SKILL.md +19 -13
  22. package/skills/skills/wiki-add-task/SKILL.md +4 -3
  23. package/skills/skills/wiki-archive/SKILL.md +23 -18
  24. package/skills/skills/wiki-audit/SKILL.md +3 -1
  25. package/skills/skills/wiki-init/SKILL.md +12 -1
  26. package/skills/skills/wiki-lint/SKILL.md +2 -1
  27. package/skills/skills/wiki-query/SKILL.md +7 -0
  28. package/skills/skills/wiki-reingest/SKILL.md +12 -8
  29. package/skills/skills/wiki-remove/SKILL.md +22 -5
  30. package/skills/skills/wiki-sync/SKILL.md +2 -3
  31. package/skills/using-skillwiki/SKILL.md +19 -13
  32. package/skills/wiki-add-task/SKILL.md +4 -3
  33. package/skills/wiki-archive/SKILL.md +23 -18
  34. package/skills/wiki-audit/SKILL.md +3 -1
  35. package/skills/wiki-init/SKILL.md +12 -1
  36. package/skills/wiki-lint/SKILL.md +2 -1
  37. package/skills/wiki-query/SKILL.md +7 -0
  38. package/skills/wiki-reingest/SKILL.md +12 -8
  39. package/skills/wiki-remove/SKILL.md +22 -5
  40. package/skills/wiki-sync/SKILL.md +2 -3
  41. package/templates/SCHEMA.md +8 -5
  42. package/templates/web-clipper/llm-wiki-clippings.json +56 -0
  43. package/templates/web-clipper/readme.txt +16 -0
@@ -6,20 +6,10 @@ import {
6
6
  import {
7
7
  atomicWriteText,
8
8
  buildRootIndexUniverse,
9
- extractFrontmatter,
10
- mapWithConcurrency,
11
9
  prepareTypedPage,
12
- readPage,
13
- readPageCached,
14
- redactSensitiveContent,
15
10
  renderRootIndex,
16
- resolveReadOnlyVaultRoot,
17
- scanSensitiveContent,
18
- scanVault,
19
- splitFrontmatter,
20
- vaultIoConcurrency,
21
11
  writeRootIndexProjection
22
- } from "./chunk-54JVBNWK.js";
12
+ } from "./chunk-QBZYEEBD.js";
23
13
  import {
24
14
  CONFIG_KEYS,
25
15
  git,
@@ -35,6 +25,39 @@ import {
35
25
  snapshotterAliasForLocalHost,
36
26
  writeDotenv
37
27
  } from "./chunk-PQG26AGJ.js";
28
+ import {
29
+ authorizeRawOperation,
30
+ buildSourceReferenceIndex,
31
+ buildSourceRelocationProjection,
32
+ classifyRawPath,
33
+ eventPathFor,
34
+ extractCitationMarkers,
35
+ hasOrphanedCitations,
36
+ hasWikilinkCitations,
37
+ isLegacyCitationStyle,
38
+ lifecycleDestination,
39
+ normalizeRawSourceTarget,
40
+ operationId,
41
+ rawSourceTargetExists,
42
+ rawSourceTargetExistsSync,
43
+ readSourceRelocations,
44
+ resolveAbsentTargetInsideVault,
45
+ resolveExistingRegularFileInsideVault,
46
+ stripFencedBlocks,
47
+ writeLogEvent
48
+ } from "./chunk-QNTBNNEL.js";
49
+ import {
50
+ extractFrontmatter,
51
+ mapWithConcurrency,
52
+ readPage,
53
+ readPageCached,
54
+ redactSensitiveContent,
55
+ resolveReadOnlyVaultRoot,
56
+ scanSensitiveContent,
57
+ scanVault,
58
+ splitFrontmatter,
59
+ vaultIoConcurrency
60
+ } from "./chunk-6ZDKTNLA.js";
38
61
  import {
39
62
  CompoundSchema,
40
63
  ExitCode,
@@ -52,9 +75,9 @@ import {
52
75
  } from "./chunk-C2DKFJFA.js";
53
76
 
54
77
  // src/commands/log-append.ts
55
- import { readFile as readFile2, stat } from "fs/promises";
78
+ import { readFile, stat } from "fs/promises";
56
79
  import { hostname } from "os";
57
- import { join as join4 } from "path";
80
+ import { join as join3 } from "path";
58
81
 
59
82
  // src/utils/last-op.ts
60
83
  import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
@@ -165,143 +188,6 @@ function releaseLogLock(handle) {
165
188
  }
166
189
  }
167
190
 
168
- // src/utils/log-events.ts
169
- import { mkdir, open, readFile, readdir } from "fs/promises";
170
- import { join as join3 } from "path";
171
- function isPlainObject(value) {
172
- return typeof value === "object" && value !== null && !Array.isArray(value);
173
- }
174
- function canonicalize(value) {
175
- if (Array.isArray(value)) return value.map(canonicalize);
176
- if (!isPlainObject(value)) return value;
177
- const out = {};
178
- for (const key of Object.keys(value).sort()) {
179
- out[key] = canonicalize(value[key]);
180
- }
181
- return out;
182
- }
183
- function eventPathFor(event) {
184
- const day = event.occurred_at.slice(0, 10);
185
- return `meta/log-events/${day}/${event.operation_id}.json`;
186
- }
187
- function validateLogEvent(event) {
188
- if (event.schema !== "skillwiki-log-event/v1") {
189
- return err("SCHEME_REJECTED", { message: "invalid event schema" });
190
- }
191
- if (!/^[0-9a-f]{64}$/.test(event.operation_id)) {
192
- return err("SCHEME_REJECTED", { message: "operation_id must be 64 hex chars" });
193
- }
194
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(event.occurred_at)) {
195
- return err("SCHEME_REJECTED", { message: "occurred_at must be UTC ISO with milliseconds" });
196
- }
197
- for (const field of ["host_id", "actor", "kind", "target", "note"]) {
198
- const v = event[field];
199
- if (typeof v !== "string" || v.trim().length === 0 || v.length > 500) {
200
- return err("SCHEME_REJECTED", { message: `invalid ${field}` });
201
- }
202
- }
203
- if (!isPlainObject(event.metadata)) {
204
- return err("SCHEME_REJECTED", { message: "metadata must be a plain object" });
205
- }
206
- const sensitive = scanSensitiveContent(JSON.stringify(event));
207
- if (sensitive.length > 0) {
208
- return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
209
- }
210
- return ok({
211
- schema: "skillwiki-log-event/v1",
212
- operation_id: event.operation_id,
213
- occurred_at: event.occurred_at,
214
- host_id: event.host_id,
215
- actor: event.actor,
216
- kind: event.kind,
217
- target: event.target,
218
- note: event.note,
219
- metadata: canonicalize(event.metadata)
220
- });
221
- }
222
- function canonicalEventJson(event) {
223
- const ordered = {
224
- schema: event.schema,
225
- operation_id: event.operation_id,
226
- occurred_at: event.occurred_at,
227
- host_id: event.host_id,
228
- actor: event.actor,
229
- kind: event.kind,
230
- target: event.target,
231
- note: event.note,
232
- metadata: event.metadata
233
- };
234
- return `${JSON.stringify(ordered, null, 2)}
235
- `;
236
- }
237
- async function writeLogEvent(vault, event) {
238
- const validated = validateLogEvent(event);
239
- if (!validated.ok) return validated;
240
- const rel = eventPathFor(validated.data);
241
- const abs = join3(vault, rel);
242
- await mkdir(join3(vault, "meta", "log-events", validated.data.occurred_at.slice(0, 10)), {
243
- recursive: true
244
- });
245
- const body = canonicalEventJson(validated.data);
246
- try {
247
- const handle = await open(abs, "wx");
248
- try {
249
- await handle.writeFile(body, "utf8");
250
- } finally {
251
- await handle.close();
252
- }
253
- return ok({ path: rel, created: true });
254
- } catch (error) {
255
- if (error.code === "EEXIST") {
256
- let existing;
257
- try {
258
- existing = await readFile(abs, "utf8");
259
- } catch (readErr) {
260
- return err("WRITE_FAILED", { path: rel, message: String(readErr) });
261
- }
262
- if (existing === body) return ok({ path: rel, created: false });
263
- return err("EVENT_IDENTITY_COLLISION", { path: rel });
264
- }
265
- return err("WRITE_FAILED", { path: rel, message: String(error) });
266
- }
267
- }
268
- async function readLogEvents(vault) {
269
- const root = join3(vault, "meta", "log-events");
270
- let days;
271
- try {
272
- days = (await readdir(root, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
273
- } catch {
274
- return ok([]);
275
- }
276
- const events = [];
277
- for (const day of days) {
278
- const files = (await readdir(join3(root, day))).filter((f) => f.endsWith(".json")).sort();
279
- for (const file of files) {
280
- const text = await readFile(join3(root, day, file), "utf8");
281
- let parsed;
282
- try {
283
- parsed = JSON.parse(text);
284
- } catch {
285
- return err("SCHEME_REJECTED", { path: `meta/log-events/${day}/${file}`, message: "invalid JSON" });
286
- }
287
- const validated = validateLogEvent(parsed);
288
- if (!validated.ok) return validated;
289
- if (eventPathFor(validated.data) !== `meta/log-events/${day}/${file}`) {
290
- return err("SCHEME_REJECTED", {
291
- path: `meta/log-events/${day}/${file}`,
292
- message: "path/identity mismatch"
293
- });
294
- }
295
- events.push(validated.data);
296
- }
297
- }
298
- events.sort((a, b) => {
299
- if (a.occurred_at !== b.occurred_at) return a.occurred_at < b.occurred_at ? -1 : 1;
300
- return a.operation_id < b.operation_id ? -1 : a.operation_id > b.operation_id ? 1 : 0;
301
- });
302
- return ok(events);
303
- }
304
-
305
191
  // src/commands/log-append.ts
306
192
  var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
307
193
  function countLogEntries(logText) {
@@ -309,29 +195,29 @@ function countLogEntries(logText) {
309
195
  for (const _ of logText.matchAll(ENTRY_RE)) count += 1;
310
196
  return count;
311
197
  }
312
- function operationMarkers(operationId) {
313
- if (!/^[0-9a-f]{64}$/.test(operationId)) {
198
+ function operationMarkers(operationId2) {
199
+ if (!/^[0-9a-f]{64}$/.test(operationId2)) {
314
200
  return err("USAGE", { message: "operationId must be a SHA-256 hex string" });
315
201
  }
316
202
  return ok([
317
- `<!-- skillwiki-log-op:${operationId} -->`,
318
- `<!-- skillwiki-page-publish:${operationId} -->`,
319
- `<!-- skillwiki-project-page-publish:${operationId} -->`
203
+ `<!-- skillwiki-log-op:${operationId2} -->`,
204
+ `<!-- skillwiki-page-publish:${operationId2} -->`,
205
+ `<!-- skillwiki-project-page-publish:${operationId2} -->`
320
206
  ]);
321
207
  }
322
- function preferredMarker(operationId, eventKind) {
208
+ function preferredMarker(operationId2, eventKind) {
323
209
  if (!eventKind || eventKind === "page-publish") {
324
- return `<!-- skillwiki-page-publish:${operationId} -->`;
210
+ return `<!-- skillwiki-page-publish:${operationId2} -->`;
325
211
  }
326
212
  if (eventKind === "project-page-publish") {
327
- return `<!-- skillwiki-project-page-publish:${operationId} -->`;
213
+ return `<!-- skillwiki-project-page-publish:${operationId2} -->`;
328
214
  }
329
- return `<!-- skillwiki-log-op:${operationId} -->`;
215
+ return `<!-- skillwiki-log-op:${operationId2} -->`;
330
216
  }
331
217
  async function appendWhileLocked(logPath, content, markers, writeMarker) {
332
218
  let logText;
333
219
  try {
334
- logText = await readFile2(logPath, "utf8");
220
+ logText = await readFile(logPath, "utf8");
335
221
  } catch {
336
222
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
337
223
  }
@@ -370,7 +256,7 @@ ${appendedContent}
370
256
  }
371
257
  async function runLogAppend(input) {
372
258
  try {
373
- await stat(join4(input.vault, "SCHEMA.md"));
259
+ await stat(join3(input.vault, "SCHEMA.md"));
374
260
  } catch {
375
261
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
376
262
  }
@@ -443,7 +329,7 @@ async function runLogAppend(input) {
443
329
  return { exitCode: ExitCode.LOG_APPEND_LOCK_HELD, result: err("LOG_APPEND_LOCK_HELD", { vault: input.vault }) };
444
330
  }
445
331
  const lockHandle = acquired.data;
446
- const logPath = join4(input.vault, "log.md");
332
+ const logPath = join3(input.vault, "log.md");
447
333
  let outcome;
448
334
  let released;
449
335
  try {
@@ -494,7 +380,7 @@ async function runLogAppend(input) {
494
380
 
495
381
  // src/utils/sync-lock.ts
496
382
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
497
- import { join as join5 } from "path";
383
+ import { join as join4 } from "path";
498
384
  import { createHash, randomBytes as randomBytes2 } from "crypto";
499
385
  function getEnvSessionId() {
500
386
  if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
@@ -517,7 +403,7 @@ function getCliSessionId(cwd) {
517
403
  return `cli-${getCwdHash(cwd)}`;
518
404
  }
519
405
  function lockPath(vault) {
520
- return join5(vault, ".skillwiki", "sync.lock");
406
+ return join4(vault, ".skillwiki", "sync.lock");
521
407
  }
522
408
  function readLock(vault) {
523
409
  const path = lockPath(vault);
@@ -536,7 +422,7 @@ function isStale(lock, now) {
536
422
  }
537
423
  function acquireLock(vault, opts = {}) {
538
424
  const path = lockPath(vault);
539
- const dir = join5(vault, ".skillwiki");
425
+ const dir = join4(vault, ".skillwiki");
540
426
  if (!existsSync3(dir)) {
541
427
  mkdirSync3(dir, { recursive: true });
542
428
  }
@@ -621,7 +507,7 @@ function acquireOwnedSyncLock(vault, opts) {
621
507
  };
622
508
  const path = lockPath(vault);
623
509
  try {
624
- mkdirSync3(join5(vault, ".skillwiki"), { recursive: true });
510
+ mkdirSync3(join4(vault, ".skillwiki"), { recursive: true });
625
511
  } catch (error) {
626
512
  return err("WRITE_FAILED", { path, message: String(error) });
627
513
  }
@@ -653,12 +539,12 @@ function releaseOwnedSyncLock(handle) {
653
539
 
654
540
  // src/commands/validate.ts
655
541
  import { createHash as createHash2 } from "crypto";
656
- import { readFile as readFile4 } from "fs/promises";
542
+ import { readFile as readFile3 } from "fs/promises";
657
543
  import { resolve, relative, sep } from "path";
658
544
 
659
545
  // src/utils/index-entry.ts
660
- import { readFile as readFile3 } from "fs/promises";
661
- import { join as join6 } from "path";
546
+ import { readFile as readFile2 } from "fs/promises";
547
+ import { join as join5 } from "path";
662
548
  var TYPE_SECTION = {
663
549
  entity: "Entities",
664
550
  concept: "Concepts",
@@ -702,10 +588,10 @@ function renderIndexUpsert(text, input) {
702
588
  });
703
589
  }
704
590
  async function upsertIndexEntry(input) {
705
- const path = join6(input.vault, "index.md");
591
+ const path = join5(input.vault, "index.md");
706
592
  let before = "";
707
593
  try {
708
- before = await readFile3(path, "utf8");
594
+ before = await readFile2(path, "utf8");
709
595
  } catch {
710
596
  before = "";
711
597
  }
@@ -740,7 +626,7 @@ var SCHEMAS = {
740
626
  async function runValidate(input) {
741
627
  let text;
742
628
  try {
743
- text = await readFile4(input.file, "utf8");
629
+ text = await readFile3(input.file, "utf8");
744
630
  } catch {
745
631
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
746
632
  }
@@ -797,7 +683,7 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
797
683
  if (relPath.startsWith("..")) {
798
684
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { reason: `file ${input.file} is not inside vault ${input.vault}` }) };
799
685
  }
800
- const operationId = createHash2("sha256").update("skillwiki-validate-apply-v1\0").update(relPath).update("\0").update(text).digest("hex");
686
+ const operationId2 = createHash2("sha256").update("skillwiki-validate-apply-v1\0").update(relPath).update("\0").update(text).digest("hex");
801
687
  if (det.schema === "typed-knowledge" || det.schema === "meta") {
802
688
  const prepared = prepareTypedPage(text, relPath);
803
689
  if (!prepared.ok) {
@@ -852,7 +738,7 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
852
738
  const logged = await runLogAppend({
853
739
  vault: input.vault,
854
740
  content: `## [${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}] validate | added: ${relPath}`,
855
- operationId,
741
+ operationId: operationId2,
856
742
  strictLock: true
857
743
  });
858
744
  if (!logged.result.ok) {
@@ -880,7 +766,7 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
880
766
  }
881
767
 
882
768
  // src/commands/graph.ts
883
- import { writeFile, mkdir as mkdir2 } from "fs/promises";
769
+ import { writeFile, mkdir } from "fs/promises";
884
770
  import { dirname } from "path";
885
771
 
886
772
  // src/parsers/wikilinks.ts
@@ -1035,7 +921,7 @@ async function runGraphBuild(input) {
1035
921
  const adamicAdar = computeAdamicAdar(adjacency);
1036
922
  const edge_count = Object.values(adjacency).reduce((acc, arr) => acc + arr.length, 0);
1037
923
  try {
1038
- await mkdir2(dirname(input.out), { recursive: true });
924
+ await mkdir(dirname(input.out), { recursive: true });
1039
925
  await writeFile(input.out, JSON.stringify({ adjacency, adamicAdar }, null, 2));
1040
926
  } catch (e) {
1041
927
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
@@ -1079,7 +965,7 @@ function computeAdamicAdar(adj) {
1079
965
  }
1080
966
 
1081
967
  // src/utils/wiki-path.ts
1082
- import { join as join7 } from "path";
968
+ import { join as join6 } from "path";
1083
969
  async function resolveInitTimePath(input) {
1084
970
  const chain = [];
1085
971
  if (input.flag !== void 0 && input.flag.length > 0) {
@@ -1092,27 +978,27 @@ async function resolveInitTimePath(input) {
1092
978
  return { path: input.envValue, source: "env", ...input.explain ? { chain } : {} };
1093
979
  }
1094
980
  if (input.explain) chain.push({ source: "env", matched: false });
1095
- const sw = await parseDotenvFile(join7(input.home, ".skillwiki", ".env"));
981
+ const sw = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
1096
982
  if (sw.WIKI_PATH !== void 0) {
1097
983
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: true, value: sw.WIKI_PATH });
1098
984
  return { path: sw.WIKI_PATH, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} };
1099
985
  }
1100
986
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: false });
1101
- const hermes = await parseDotenvFile(join7(input.home, ".hermes", ".env"));
987
+ const hermes = await parseDotenvFile(join6(input.home, ".hermes", ".env"));
1102
988
  if (hermes.WIKI_PATH !== void 0) {
1103
989
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: true, value: hermes.WIKI_PATH });
1104
990
  return { path: hermes.WIKI_PATH, source: "hermes-dotenv", ...input.explain ? { chain } : {} };
1105
991
  }
1106
992
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: false });
1107
993
  if (input.cwd) {
1108
- const projCfg = await parseDotenvFile(join7(input.cwd, ".skillwiki", ".env"));
994
+ const projCfg = await parseDotenvFile(join6(input.cwd, ".skillwiki", ".env"));
1109
995
  if (projCfg.WIKI_PATH !== void 0) {
1110
996
  if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
1111
997
  return { path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} };
1112
998
  }
1113
999
  }
1114
1000
  if (input.explain) chain.push({ source: "project-dotenv", matched: false });
1115
- const fallback = join7(input.home, "wiki");
1001
+ const fallback = join6(input.home, "wiki");
1116
1002
  if (input.explain) chain.push({ source: "default", matched: true, value: fallback });
1117
1003
  return { path: fallback, source: "default", ...input.explain ? { chain } : {} };
1118
1004
  }
@@ -1123,7 +1009,7 @@ async function resolveRuntimePath(input) {
1123
1009
  return ok({ path: input.flag, source: "flag", ...input.explain ? { chain } : {} });
1124
1010
  }
1125
1011
  if (input.explain) chain.push({ source: "flag", matched: false });
1126
- const swGlobal = await parseDotenvFile(join7(input.home, ".skillwiki", ".env"));
1012
+ const swGlobal = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
1127
1013
  const wikiName = input.wiki;
1128
1014
  if (wikiName !== void 0 && wikiName.length > 0) {
1129
1015
  if (wikiName.toLowerCase() === "default") {
@@ -1167,7 +1053,7 @@ async function resolveRuntimePath(input) {
1167
1053
  }
1168
1054
  if (input.explain) chain.push({ source: "env", matched: false });
1169
1055
  if (input.cwd) {
1170
- const projCfg = await parseDotenvFile(join7(input.cwd, ".skillwiki", ".env"));
1056
+ const projCfg = await parseDotenvFile(join6(input.cwd, ".skillwiki", ".env"));
1171
1057
  if (projCfg.WIKI_PATH !== void 0) {
1172
1058
  if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
1173
1059
  return ok({ path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} });
@@ -1287,156 +1173,12 @@ function simulateRemoval(adj, removed) {
1287
1173
  }
1288
1174
 
1289
1175
  // src/commands/audit.ts
1290
- import { readFile as readFile5, stat as stat3 } from "fs/promises";
1291
- import { dirname as dirname2, resolve as resolve2, join as join9 } from "path";
1292
-
1293
- // src/parsers/citations.ts
1294
- var FENCE2 = /```[\s\S]*?```/g;
1295
- var INLINE_CODE = /``[^`\n]+``|`[^`\n]+`/g;
1296
- var MARKER_RE = /\^\[(raw\/[^\]]+)\]/g;
1297
- var FRONTMATTER = /^---\n[\s\S]*?\n---\n?/;
1298
- function stripFences(body) {
1299
- return body.replace(FENCE2, "").replace(INLINE_CODE, "");
1300
- }
1301
- var TILDE_FENCE = /~~~[\s\S]*?~~~/g;
1302
- function stripFencedBlocks(body) {
1303
- return body.replace(FENCE2, "").replace(TILDE_FENCE, "");
1304
- }
1305
- function extractCitationMarkers(body) {
1306
- const stripped = stripFences(body);
1307
- const out = [];
1308
- let m;
1309
- while ((m = MARKER_RE.exec(stripped)) !== null) {
1310
- if (m[1] === "raw/...") continue;
1311
- out.push({ marker: m[0], target: m[1] });
1312
- }
1313
- return out;
1314
- }
1315
- function hasSourcesFooter(body) {
1316
- return /^## Sources\s*$/m.test(stripFencedBlocks(body));
1317
- }
1318
- function isLegacyCitationStyle(body) {
1319
- const markers = extractCitationMarkers(body);
1320
- if (markers.length === 0) return false;
1321
- if (!hasSourcesFooter(body)) return true;
1322
- const lines = stripFences(body.replace(FRONTMATTER, "")).split("\n");
1323
- let inSources = false;
1324
- let lastNonBlankWasTable = false;
1325
- for (const line of lines) {
1326
- if (/^## Sources\b/.test(line.trim())) {
1327
- inSources = true;
1328
- continue;
1329
- }
1330
- if (inSources) continue;
1331
- const matches = [...line.matchAll(MARKER_RE)];
1332
- if (matches.length === 0) {
1333
- if (line.trim().length > 0) lastNonBlankWasTable = /^\|/.test(line.trim());
1334
- continue;
1335
- }
1336
- const markerOnly = line.replace(MARKER_RE, "").trim();
1337
- if (markerOnly.length === 0 && !lastNonBlankWasTable) return true;
1338
- lastNonBlankWasTable = false;
1339
- const lastMatch = matches[matches.length - 1];
1340
- const afterLast = line.slice(lastMatch.index + lastMatch[0].length).replace(MARKER_RE, "").trim();
1341
- if (afterLast.length > 0) return true;
1342
- const beforeFirst = line.slice(0, matches[0].index).trim();
1343
- if (beforeFirst.length > 0 && !/[.!?]["'"]*\s*$/.test(beforeFirst)) return true;
1344
- }
1345
- return false;
1346
- }
1347
- function hasOrphanedCitations(body) {
1348
- const noFm = body.replace(FRONTMATTER, "");
1349
- const stripped = stripFences(noFm);
1350
- const lines = stripped.split("\n");
1351
- const rawLines = noFm.split("\n");
1352
- let inSources = false;
1353
- let sourcesEnded = false;
1354
- let sourcesStartLine = -1;
1355
- let lastNonBlankInSources = -1;
1356
- for (let i = 0; i < lines.length; i++) {
1357
- const line = lines[i];
1358
- const trimmed = line.trim();
1359
- if (/^## Sources\b/.test(trimmed)) {
1360
- inSources = true;
1361
- sourcesStartLine = i;
1362
- continue;
1363
- }
1364
- if (!inSources || sourcesEnded) continue;
1365
- if (trimmed.length === 0) {
1366
- if (lastNonBlankInSources >= 0) {
1367
- sourcesEnded = true;
1368
- }
1369
- continue;
1370
- }
1371
- const isListItem = /^\s*(?:[-*]|\d+\.)\s+/.test(line);
1372
- const hasMarker = /\^\[raw\//.test(line);
1373
- const hasBacktickRawPath = /`raw\/[^`]+`/.test(rawLines[i]);
1374
- if (isListItem && hasMarker) {
1375
- lastNonBlankInSources = i;
1376
- } else if (isListItem && hasBacktickRawPath) {
1377
- lastNonBlankInSources = i;
1378
- } else if (hasMarker && !isListItem) {
1379
- return true;
1380
- } else {
1381
- sourcesEnded = true;
1382
- }
1383
- }
1384
- if (sourcesStartLine === -1) return false;
1385
- if (sourcesEnded) {
1386
- const scanStart = Math.max(lastNonBlankInSources + 1, sourcesStartLine + 1);
1387
- for (let i = scanStart; i < lines.length; i++) {
1388
- if (/\^\[raw\//.test(lines[i])) {
1389
- return true;
1390
- }
1391
- }
1392
- }
1393
- return false;
1394
- }
1395
- function hasWikilinkCitations(body) {
1396
- const stripped = stripFences(body);
1397
- return /\[\[raw\/[^\]]+\]\]/.test(stripped);
1398
- }
1399
-
1400
- // src/utils/raw-source.ts
1401
- import { existsSync as existsSync4 } from "fs";
1402
- import { stat as stat2 } from "fs/promises";
1403
- import { join as join8 } from "path";
1404
- function normalizeRawSourceTarget(entry) {
1405
- let target = entry.trim().replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
1406
- target = target.replace(/^\^\[/, "").replace(/\]$/, "");
1407
- if (!target.startsWith("raw/") && !target.startsWith("_archive/raw/")) return null;
1408
- return target;
1409
- }
1410
- function rawSourceTargetCandidates(vault, target) {
1411
- const normalized = normalizeRawSourceTarget(target);
1412
- if (!normalized) return [];
1413
- const candidates = [join8(vault, normalized)];
1414
- if (!normalized.endsWith(".md")) candidates.push(join8(vault, `${normalized}.md`));
1415
- if (normalized.startsWith("raw/")) {
1416
- candidates.push(join8(vault, "_archive", normalized));
1417
- if (!normalized.endsWith(".md")) candidates.push(join8(vault, "_archive", `${normalized}.md`));
1418
- }
1419
- return [...new Set(candidates)];
1420
- }
1421
- function rawSourceTargetExistsSync(vault, target) {
1422
- return rawSourceTargetCandidates(vault, target).some((candidate) => existsSync4(candidate));
1423
- }
1424
- async function rawSourceTargetExists(vault, target) {
1425
- for (const candidate of rawSourceTargetCandidates(vault, target)) {
1426
- try {
1427
- await stat2(candidate);
1428
- return true;
1429
- } catch {
1430
- }
1431
- }
1432
- return false;
1433
- }
1434
-
1435
- // src/commands/audit.ts
1176
+ import { readFile as readFile4, stat as stat2 } from "fs/promises";
1177
+ import { dirname as dirname2, resolve as resolve2, join as join7 } from "path";
1436
1178
  async function runAudit(input) {
1437
1179
  let text;
1438
1180
  try {
1439
- text = await readFile5(input.file, "utf8");
1181
+ text = await readFile4(input.file, "utf8");
1440
1182
  } catch {
1441
1183
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
1442
1184
  }
@@ -1489,7 +1231,7 @@ async function findVaultRoot(start) {
1489
1231
  let cur = start;
1490
1232
  for (let i = 0; i < 20; i++) {
1491
1233
  try {
1492
- await stat3(join9(cur, "SCHEMA.md"));
1234
+ await stat2(join7(cur, "SCHEMA.md"));
1493
1235
  return cur;
1494
1236
  } catch {
1495
1237
  }
@@ -1581,8 +1323,8 @@ ${broken.map((b) => ` ${b.page}:[[${b.slug}]] (line ${b.line})`).join("\n")}` }
1581
1323
  }
1582
1324
 
1583
1325
  // src/commands/tag-audit.ts
1584
- import { readFile as readFile6 } from "fs/promises";
1585
- import { join as join10 } from "path";
1326
+ import { readFile as readFile5 } from "fs/promises";
1327
+ import { join as join8 } from "path";
1586
1328
 
1587
1329
  // src/parsers/taxonomy.ts
1588
1330
  import yaml from "js-yaml";
@@ -1607,12 +1349,12 @@ function parseTaxonomyDocument(schemaText) {
1607
1349
  const nextHeading = /^#{1,2}[ \t]+/m.exec(unboundedTail);
1608
1350
  const sectionEnd = nextHeading?.index === void 0 ? schemaText.length : afterHeading + nextHeading.index;
1609
1351
  const sectionText = schemaText.slice(afterHeading, sectionEnd);
1610
- const open2 = /^```yaml[ \t]*\r?$/m.exec(sectionText);
1611
- if (!open2 || open2.index === void 0) {
1352
+ const open = /^```yaml[ \t]*\r?$/m.exec(sectionText);
1353
+ if (!open || open.index === void 0) {
1612
1354
  return err("NO_TAXONOMY_BLOCK", { message: "Fenced YAML taxonomy block not found" });
1613
1355
  }
1614
- const openStart = afterHeading + open2.index;
1615
- const yamlStart = openStart + open2[0].length + 1;
1356
+ const openStart = afterHeading + open.index;
1357
+ const yamlStart = openStart + open[0].length + 1;
1616
1358
  const afterOpen = schemaText.slice(yamlStart, sectionEnd);
1617
1359
  const close = /^```[ \t]*\r?$/m.exec(afterOpen);
1618
1360
  if (!close || close.index === void 0) {
@@ -1735,7 +1477,7 @@ async function runTagAudit(input) {
1735
1477
  const scanResult = input.scan ? ok(input.scan) : await scanVault(input.vault);
1736
1478
  if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
1737
1479
  const scan = scanResult.data;
1738
- const schemaText = await readFile6(join10(input.vault, "SCHEMA.md"), "utf8");
1480
+ const schemaText = await readFile5(join8(input.vault, "SCHEMA.md"), "utf8");
1739
1481
  const tax = extractTaxonomy(schemaText);
1740
1482
  if (!tax.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: tax };
1741
1483
  const allowed = new Set(tax.data);
@@ -1767,8 +1509,8 @@ async function runTagAudit(input) {
1767
1509
  }
1768
1510
 
1769
1511
  // src/commands/index-check.ts
1770
- import { readFile as readFile7 } from "fs/promises";
1771
- import { join as join11 } from "path";
1512
+ import { readFile as readFile6 } from "fs/promises";
1513
+ import { join as join9 } from "path";
1772
1514
  function normalizeIndexTarget(raw) {
1773
1515
  return raw.replace(/\.md$/, "").replace(/^\.?\//, "");
1774
1516
  }
@@ -1780,7 +1522,7 @@ async function runIndexCheck(input) {
1780
1522
  }
1781
1523
  let indexText = "";
1782
1524
  try {
1783
- indexText = await readFile7(join11(input.vault, "index.md"), "utf8");
1525
+ indexText = await readFile6(join9(input.vault, "index.md"), "utf8");
1784
1526
  } catch {
1785
1527
  }
1786
1528
  const indexTargets = /* @__PURE__ */ new Set();
@@ -1834,20 +1576,20 @@ async function runIndexCheck(input) {
1834
1576
  }
1835
1577
 
1836
1578
  // src/commands/project-index.ts
1837
- import { readdir as readdir2, readFile as readFile8, mkdir as mkdir3 } from "fs/promises";
1838
- import { join as join12, dirname as dirname3, basename } from "path";
1579
+ import { readdir, readFile as readFile7, mkdir as mkdir2 } from "fs/promises";
1580
+ import { join as join10, dirname as dirname3, basename } from "path";
1839
1581
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
1840
1582
  var PROJECT_LOCAL_DIRS = ["requirements", "work", "architecture", "history"];
1841
1583
  async function scanMarkdownTree(rootAbs, rootRel) {
1842
1584
  const found = [];
1843
1585
  let entries;
1844
1586
  try {
1845
- entries = await readdir2(rootAbs, { withFileTypes: true });
1587
+ entries = await readdir(rootAbs, { withFileTypes: true });
1846
1588
  } catch {
1847
1589
  return found;
1848
1590
  }
1849
1591
  for (const entry of entries) {
1850
- const abs = join12(rootAbs, entry.name);
1592
+ const abs = join10(rootAbs, entry.name);
1851
1593
  const rel = `${rootRel}/${entry.name}`;
1852
1594
  if (entry.isDirectory()) {
1853
1595
  found.push(...await scanMarkdownTree(abs, rel));
@@ -1876,23 +1618,23 @@ function projectLocalType(slug, page, data) {
1876
1618
  return typeof data.type === "string" ? data.type : "project";
1877
1619
  }
1878
1620
  async function renderProjectIndex(vault, slug, opts = {}) {
1879
- const projectDir = join12(vault, "projects", slug);
1621
+ const projectDir = join10(vault, "projects", slug);
1880
1622
  try {
1881
- await readdir2(projectDir);
1623
+ await readdir(projectDir);
1882
1624
  } catch {
1883
1625
  return err("PROJECT_NOT_FOUND", { slug, path: projectDir });
1884
1626
  }
1885
1627
  const wikilinkPattern = `[[${slug}]]`;
1886
1628
  const entries = [];
1887
- const compoundDir = join12(vault, "projects", slug, "compound");
1629
+ const compoundDir = join10(vault, "projects", slug, "compound");
1888
1630
  try {
1889
- const compoundFiles = await readdir2(compoundDir, { withFileTypes: true });
1631
+ const compoundFiles = await readdir(compoundDir, { withFileTypes: true });
1890
1632
  for (const entry of compoundFiles) {
1891
1633
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1892
- const filePath = join12(compoundDir, entry.name);
1634
+ const filePath = join10(compoundDir, entry.name);
1893
1635
  let text;
1894
1636
  try {
1895
- text = await readFile8(filePath, "utf8");
1637
+ text = await readFile7(filePath, "utf8");
1896
1638
  } catch {
1897
1639
  continue;
1898
1640
  }
@@ -1909,16 +1651,16 @@ async function renderProjectIndex(vault, slug, opts = {}) {
1909
1651
  for (const dir of LAYER2_DIRS) {
1910
1652
  let files;
1911
1653
  try {
1912
- files = await readdir2(join12(vault, dir), { withFileTypes: true });
1654
+ files = await readdir(join10(vault, dir), { withFileTypes: true });
1913
1655
  } catch {
1914
1656
  continue;
1915
1657
  }
1916
1658
  for (const entry of files) {
1917
1659
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1918
- const filePath = join12(vault, dir, entry.name);
1660
+ const filePath = join10(vault, dir, entry.name);
1919
1661
  let text;
1920
1662
  try {
1921
- text = await readFile8(filePath, "utf8");
1663
+ text = await readFile7(filePath, "utf8");
1922
1664
  } catch {
1923
1665
  continue;
1924
1666
  }
@@ -1934,14 +1676,14 @@ async function renderProjectIndex(vault, slug, opts = {}) {
1934
1676
  }
1935
1677
  }
1936
1678
  for (const dir of PROJECT_LOCAL_DIRS) {
1937
- const rootAbs = join12(projectDir, dir);
1679
+ const rootAbs = join10(projectDir, dir);
1938
1680
  const rootRel = `projects/${slug}/${dir}`;
1939
1681
  const pages = await scanMarkdownTree(rootAbs, rootRel);
1940
1682
  for (const page of pages) {
1941
- const filePath = join12(vault, page);
1683
+ const filePath = join10(vault, page);
1942
1684
  let text;
1943
1685
  try {
1944
- text = await readFile8(filePath, "utf8");
1686
+ text = await readFile7(filePath, "utf8");
1945
1687
  } catch {
1946
1688
  continue;
1947
1689
  }
@@ -2014,7 +1756,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
2014
1756
  }
2015
1757
  async function runProjectIndex(input) {
2016
1758
  const slug = input.slug;
2017
- const projectDir = join12(input.vault, "projects", slug);
1759
+ const projectDir = join10(input.vault, "projects", slug);
2018
1760
  const rendered = await renderProjectIndex(input.vault, slug);
2019
1761
  if (!rendered.ok) {
2020
1762
  return {
@@ -2022,12 +1764,12 @@ async function runProjectIndex(input) {
2022
1764
  result: rendered
2023
1765
  };
2024
1766
  }
2025
- const indexPath = join12(projectDir, "knowledge.md");
1767
+ const indexPath = join10(projectDir, "knowledge.md");
2026
1768
  const entries = rendered.data.entries;
2027
1769
  let existing = false;
2028
1770
  let stale = false;
2029
1771
  try {
2030
- const existingText = await readFile8(indexPath, "utf8");
1772
+ const existingText = await readFile7(indexPath, "utf8");
2031
1773
  existing = true;
2032
1774
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
2033
1775
  const existingPages = new Set(existingEntries.map((l) => {
@@ -2040,7 +1782,7 @@ async function runProjectIndex(input) {
2040
1782
  }
2041
1783
  if (input.apply) {
2042
1784
  try {
2043
- await mkdir3(dirname3(indexPath), { recursive: true });
1785
+ await mkdir2(dirname3(indexPath), { recursive: true });
2044
1786
  } catch (e) {
2045
1787
  return {
2046
1788
  exitCode: ExitCode.WRITE_FAILED,
@@ -2072,8 +1814,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
2072
1814
  }
2073
1815
 
2074
1816
  // src/commands/stale.ts
2075
- import { readdir as readdir3, rename, mkdir as mkdir4, readFile as readFile9 } from "fs/promises";
2076
- import { join as join13 } from "path";
1817
+ import { readdir as readdir2, rename, mkdir as mkdir4, readFile as readFile9 } from "fs/promises";
1818
+ import { join as join11 } from "path";
2077
1819
 
2078
1820
  // src/parsers/expiry-annotations.ts
2079
1821
  var HEADING_RE = /^#{1,6}\s+(.+)$/;
@@ -2109,6 +1851,144 @@ function parseExpiryAnnotations(content, pagePath) {
2109
1851
  return annotations;
2110
1852
  }
2111
1853
 
1854
+ // src/utils/raw-structural-transaction.ts
1855
+ import { createHash as createHash3 } from "crypto";
1856
+ import { copyFile, mkdir as mkdir3, readFile as readFile8, unlink } from "fs/promises";
1857
+ import { constants } from "fs";
1858
+ import { dirname as dirname4 } from "path";
1859
+ function sha256(bytes) {
1860
+ return createHash3("sha256").update(bytes).digest("hex");
1861
+ }
1862
+ function token(plan) {
1863
+ const payload = Buffer.from(JSON.stringify(plan), "utf8").toString("base64url");
1864
+ return `swraw1.${payload}.${sha256(Buffer.from(payload, "utf8"))}`;
1865
+ }
1866
+ async function planRawStructuralMove(input) {
1867
+ const sourcePath = classifyRawPath(input.source);
1868
+ if (!sourcePath.ok) return sourcePath;
1869
+ const destinationPath = classifyRawPath(input.destination);
1870
+ if (!destinationPath.ok) return destinationPath;
1871
+ if (sourcePath.data.category === "assets" || destinationPath.data.category === "assets") {
1872
+ return err("RAW_ASSET_PATH_FROZEN", {
1873
+ source: input.source,
1874
+ destination: input.destination,
1875
+ message: "referenced asset paths are stable; routine raw lifecycle moves never move assets"
1876
+ });
1877
+ }
1878
+ if (sourcePath.data.storage !== "active") {
1879
+ return err("RAW_SOURCE_NOT_ACTIVE", { path: input.source, storage: sourcePath.data.storage });
1880
+ }
1881
+ if (sourcePath.data.category !== destinationPath.data.category) {
1882
+ return err("RAW_MOVE_CATEGORY_MISMATCH", {
1883
+ source: input.source,
1884
+ destination: input.destination,
1885
+ source_category: sourcePath.data.category,
1886
+ destination_category: destinationPath.data.category
1887
+ });
1888
+ }
1889
+ const expectedStorage = input.operation === "archive" ? "archived" : input.operation === "deduplicate" ? "duplicate" : "active";
1890
+ if (destinationPath.data.storage !== expectedStorage) {
1891
+ return err("RAW_MOVE_DESTINATION_INVALID", {
1892
+ operation: input.operation,
1893
+ destination: input.destination,
1894
+ expected_storage: expectedStorage,
1895
+ actual_storage: destinationPath.data.storage
1896
+ });
1897
+ }
1898
+ const allowed = authorizeRawOperation({
1899
+ operationClass: "preserve-move",
1900
+ trigger: "attended-apply",
1901
+ source: input.source,
1902
+ destination: input.destination
1903
+ });
1904
+ if (!allowed.ok) return allowed;
1905
+ if (input.source === input.destination) return err("RAW_MOVE_NOOP", { path: input.source });
1906
+ const sourceResolved = await resolveExistingRegularFileInsideVault(input.vault, input.source);
1907
+ if (!sourceResolved.ok) return err("RAW_SOURCE_UNSAFE", { path: input.source, cause: sourceResolved });
1908
+ const destinationResolved = await resolveAbsentTargetInsideVault(input.vault, input.destination);
1909
+ if (!destinationResolved.ok) {
1910
+ return destinationResolved.error === "RAW_DESTINATION_EXISTS" ? destinationResolved : err("RAW_DESTINATION_UNSAFE", { path: input.destination, cause: destinationResolved });
1911
+ }
1912
+ const sourceBytes = await readFile8(sourceResolved.data);
1913
+ const sourceSha = sha256(sourceBytes);
1914
+ const operation_id = operationId("raw-structural", [input.operation, input.source, input.destination, sourceSha]);
1915
+ const base = {
1916
+ operation: input.operation,
1917
+ source: input.source,
1918
+ destination: input.destination,
1919
+ source_sha256: sourceSha,
1920
+ destination_state: "absent",
1921
+ operation_id
1922
+ };
1923
+ return ok({ ...base, approval_token: token(base) });
1924
+ }
1925
+ async function applyRawStructuralMove(input) {
1926
+ const planned = await planRawStructuralMove(input);
1927
+ if (!planned.ok) return planned;
1928
+ if (input.approve !== planned.data.approval_token) {
1929
+ return err("APPROVAL_INVALID", { message: "raw structural approval does not match live state" });
1930
+ }
1931
+ const sourceResolved = await resolveExistingRegularFileInsideVault(input.vault, input.source);
1932
+ if (!sourceResolved.ok) return err("RAW_SOURCE_UNSAFE", { path: input.source, cause: sourceResolved });
1933
+ const destinationResolved = await resolveAbsentTargetInsideVault(input.vault, input.destination);
1934
+ if (!destinationResolved.ok) {
1935
+ return destinationResolved.error === "RAW_DESTINATION_EXISTS" ? destinationResolved : err("RAW_DESTINATION_UNSAFE", { path: input.destination, cause: destinationResolved });
1936
+ }
1937
+ const sourceAbs = sourceResolved.data;
1938
+ const destinationAbs = destinationResolved.data;
1939
+ await mkdir3(dirname4(destinationAbs), { recursive: true });
1940
+ try {
1941
+ await copyFile(sourceAbs, destinationAbs, constants.COPYFILE_EXCL);
1942
+ } catch (error) {
1943
+ return err("RAW_MOVE_COPY_FAILED", { source: input.source, destination: input.destination, message: String(error) });
1944
+ }
1945
+ const destinationBytes = await readFile8(destinationAbs);
1946
+ const destinationSha = sha256(destinationBytes);
1947
+ if (destinationSha !== planned.data.source_sha256) {
1948
+ return err("RAW_MOVE_HASH_MISMATCH", { source: input.source, destination: input.destination, source_sha256: planned.data.source_sha256, destination_sha256: destinationSha });
1949
+ }
1950
+ const occurredAt = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
1951
+ const event = await (input.writeEvent ?? writeLogEvent)(input.vault, {
1952
+ schema: "skillwiki-log-event/v1",
1953
+ operation_id: planned.data.operation_id,
1954
+ occurred_at: occurredAt,
1955
+ host_id: input.hostId ?? "local",
1956
+ actor: input.actor ?? "skillwiki-cli",
1957
+ kind: "source-relocation",
1958
+ target: input.destination,
1959
+ note: input.reason ?? `${input.operation} ${input.source} to ${input.destination}`,
1960
+ metadata: {
1961
+ operation: input.operation,
1962
+ previous_path: input.source,
1963
+ current_path: input.destination,
1964
+ source_sha256: planned.data.source_sha256,
1965
+ destination_sha256: destinationSha,
1966
+ command: input.command ?? "skillwiki",
1967
+ authority: "attended-apply",
1968
+ citation_changes: input.citationChanges ?? []
1969
+ }
1970
+ });
1971
+ if (!event.ok) return event;
1972
+ try {
1973
+ await unlink(sourceAbs);
1974
+ } catch (error) {
1975
+ return err("RAW_MOVE_SOURCE_RETAINED", {
1976
+ source: input.source,
1977
+ destination: input.destination,
1978
+ event_path: event.data.path,
1979
+ message: String(error)
1980
+ });
1981
+ }
1982
+ return ok({
1983
+ operation: planned.data.operation,
1984
+ source: planned.data.source,
1985
+ destination: planned.data.destination,
1986
+ source_sha256: planned.data.source_sha256,
1987
+ destination_sha256: destinationSha,
1988
+ event_path: event.data.path
1989
+ });
1990
+ }
1991
+
2112
1992
  // src/commands/stale.ts
2113
1993
  function daysSince(isoDate) {
2114
1994
  return Math.floor((Date.now() - Date.parse(isoDate)) / 864e5);
@@ -2122,10 +2002,10 @@ async function runStale(input) {
2122
2002
  const archived = [];
2123
2003
  const workDirs = /* @__PURE__ */ new Map();
2124
2004
  const workDirsBySlug = /* @__PURE__ */ new Map();
2125
- const projectsDir = join13(input.vault, "projects");
2005
+ const projectsDir = join11(input.vault, "projects");
2126
2006
  let projectSlugs = [];
2127
2007
  try {
2128
- projectSlugs = (await readdir3(projectsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
2008
+ projectSlugs = (await readdir2(projectsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
2129
2009
  } catch {
2130
2010
  }
2131
2011
  if (input.project) {
@@ -2135,10 +2015,10 @@ async function runStale(input) {
2135
2015
  projectSlugs = [input.project];
2136
2016
  }
2137
2017
  for (const slug of projectSlugs) {
2138
- const workPath = join13(projectsDir, slug, "work");
2018
+ const workPath = join11(projectsDir, slug, "work");
2139
2019
  let entries;
2140
2020
  try {
2141
- entries = await readdir3(workPath, { withFileTypes: true });
2021
+ entries = await readdir2(workPath, { withFileTypes: true });
2142
2022
  } catch {
2143
2023
  continue;
2144
2024
  }
@@ -2146,11 +2026,11 @@ async function runStale(input) {
2146
2026
  for (const e of entries) {
2147
2027
  if (!e.isDirectory()) continue;
2148
2028
  const relDir = `projects/${slug}/work/${e.name}`;
2149
- const absDir = join13(workPath, e.name);
2029
+ const absDir = join11(workPath, e.name);
2150
2030
  let status = "";
2151
2031
  let files;
2152
2032
  try {
2153
- files = await readdir3(absDir);
2033
+ files = await readdir2(absDir);
2154
2034
  } catch {
2155
2035
  workDirs.set(relDir, "");
2156
2036
  slugDirs.set(e.name, "");
@@ -2159,7 +2039,7 @@ async function runStale(input) {
2159
2039
  for (const f of files) {
2160
2040
  if (!f.endsWith(".md")) continue;
2161
2041
  try {
2162
- const fm = extractFrontmatter(await readFile9(join13(absDir, f), "utf8"));
2042
+ const fm = extractFrontmatter(await readFile9(join11(absDir, f), "utf8"));
2163
2043
  if (fm.ok && typeof fm.data.status === "string") {
2164
2044
  status = fm.data.status;
2165
2045
  break;
@@ -2255,7 +2135,7 @@ async function runStale(input) {
2255
2135
  }
2256
2136
  }
2257
2137
  await mapWithConcurrency([...workDirs.keys()], vaultIoConcurrency(), async (relDir) => {
2258
- const specPath = join13(input.vault, relDir, "spec.md");
2138
+ const specPath = join11(input.vault, relDir, "spec.md");
2259
2139
  try {
2260
2140
  const specContent = await readFile9(specPath, "utf8");
2261
2141
  const specFm = extractFrontmatter(specContent);
@@ -2286,7 +2166,7 @@ async function runStale(input) {
2286
2166
  if (daysSince(dateStr) < input.days) continue;
2287
2167
  let files;
2288
2168
  try {
2289
- files = await readdir3(join13(input.vault, relDir));
2169
+ files = await readdir2(join11(input.vault, relDir));
2290
2170
  } catch {
2291
2171
  continue;
2292
2172
  }
@@ -2356,48 +2236,92 @@ async function runStale(input) {
2356
2236
  staleSections.push(...staleSectionResults.flat());
2357
2237
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2358
2238
  if (input.archive) {
2359
- const archiveDir = join13(input.vault, "_archive", today);
2360
- await mkdir4(archiveDir, { recursive: true });
2361
- const citedRawPaths = /* @__PURE__ */ new Set();
2362
- for (const page of scan.typedKnowledge) {
2363
- const text = await readPageCached(page, input.pageTextCache).catch(() => "");
2364
- for (const line of text.split("\n")) {
2365
- for (const m of line.matchAll(/\^\[(raw\/[^\]]+)\]/g)) {
2366
- citedRawPaths.add(m[1]);
2367
- }
2368
- for (const m of line.matchAll(/raw\/[^\s,\]"]+\.md/g)) {
2369
- citedRawPaths.add(m[0]);
2370
- }
2371
- }
2372
- }
2239
+ const archiveDir = join11(input.vault, "_archive", today);
2240
+ const relocations = await readSourceRelocations(input.vault);
2241
+ if (!relocations.ok) return { exitCode: ExitCode.WRITE_FAILED, result: relocations };
2242
+ const typedPaths = new Set(scan.typedKnowledge.map((page) => page.relPath));
2243
+ const references = await buildSourceReferenceIndex({
2244
+ typedPages: scan.typedKnowledge,
2245
+ otherPages: scan.allMarkdown.filter((page) => !typedPaths.has(page.relPath) && !page.relPath.startsWith("raw/")),
2246
+ availableRawPaths: scan.raw.map((page) => page.relPath),
2247
+ relocationProjection: buildSourceRelocationProjection(relocations.data)
2248
+ });
2249
+ const citedRawPaths = /* @__PURE__ */ new Set([
2250
+ ...references.integratedBy.keys(),
2251
+ ...references.referencedElsewhereBy.keys()
2252
+ ]);
2253
+ const rawPlans = [];
2373
2254
  for (const t of staleTranscripts) {
2374
2255
  if (citedRawPaths.has(t.path) || citedRawPaths.has(t.path.replace(/\.md$/, ""))) continue;
2375
- const dest = join13(archiveDir, t.path.split("/").pop());
2376
- try {
2377
- await rename(join13(input.vault, t.path), dest);
2378
- archived.push(t.path);
2379
- } catch {
2380
- }
2256
+ const destination = lifecycleDestination(t.path, "archive");
2257
+ if (!destination.ok) return { exitCode: ExitCode.WRITE_FAILED, result: destination };
2258
+ const plan = await planRawStructuralMove({ vault: input.vault, operation: "archive", source: t.path, destination: destination.data });
2259
+ if (!plan.ok) return { exitCode: ExitCode.WRITE_FAILED, result: plan };
2260
+ rawPlans.push({
2261
+ from: t.path,
2262
+ to: destination.data,
2263
+ approval: plan.data.approval_token,
2264
+ sourceSha256: plan.data.source_sha256
2265
+ });
2266
+ }
2267
+ const workPlans = [...incompleteWorkItems, ...doneWorkItems].map((w) => w.path).sort();
2268
+ const approvalToken = operationId("stale-archive-approval", [
2269
+ today,
2270
+ ...rawPlans.flatMap((plan) => [plan.from, plan.to, plan.sourceSha256, plan.approval]),
2271
+ ...workPlans
2272
+ ]);
2273
+ if (!input.apply) {
2274
+ const total2 = stale.length + staleTranscripts.length + unclaimedTranscripts.length + incompleteWorkItems.length + doneWorkItems.length + staleSections.length;
2275
+ return { exitCode: total2 > 0 ? ExitCode.STALE_PAGE : ExitCode.OK, result: ok({
2276
+ stale: [...stale, ...staleTranscripts.map((t) => ({ page: t.path, reason: t.reason })), ...unclaimedTranscripts.map((t) => ({ page: t.path, reason: t.reason })), ...incompleteWorkItems.map((w) => ({ page: w.path, reason: w.reason })), ...doneWorkItems.map((w) => ({ page: w.path, reason: w.reason }))],
2277
+ stale_transcripts: staleTranscripts,
2278
+ unclaimed_transcripts: unclaimedTranscripts,
2279
+ incomplete_work_items: incompleteWorkItems,
2280
+ done_work_items: doneWorkItems,
2281
+ stale_sections: staleSections,
2282
+ archived: [],
2283
+ planned_archives: rawPlans.map((plan) => ({ from: plan.from, to: plan.to })),
2284
+ approval_token: approvalToken,
2285
+ humanHint: `DRY-RUN \u2014 ${rawPlans.length} raw transcript archive(s) and ${workPlans.length} work archive(s) require --apply --approve ${approvalToken}`
2286
+ }) };
2287
+ }
2288
+ if (input.approve !== approvalToken) {
2289
+ return { exitCode: ExitCode.USAGE, result: { ok: false, error: "APPROVAL_INVALID", detail: { message: "stale archive approval does not match live state", approval_token: approvalToken } } };
2290
+ }
2291
+ await mkdir4(archiveDir, { recursive: true });
2292
+ for (const plan of rawPlans) {
2293
+ const moved = await applyRawStructuralMove({
2294
+ vault: input.vault,
2295
+ operation: "archive",
2296
+ source: plan.from,
2297
+ destination: plan.to,
2298
+ approve: plan.approval,
2299
+ command: "skillwiki stale --archive --apply"
2300
+ });
2301
+ if (!moved.ok) return { exitCode: ExitCode.WRITE_FAILED, result: moved };
2302
+ archived.push(plan.from);
2381
2303
  }
2382
2304
  for (const w of [...incompleteWorkItems, ...doneWorkItems]) {
2383
2305
  const parts = w.path.split("/");
2384
2306
  if (parts.length >= 4 && parts[0] === "projects") {
2385
2307
  const slug = parts[1];
2386
2308
  const itemName = parts[3];
2387
- const histDir = join13(input.vault, "projects", slug, "history", "archived-work");
2309
+ const histDir = join11(input.vault, "projects", slug, "history", "archived-work");
2388
2310
  await mkdir4(histDir, { recursive: true });
2389
- const dest = join13(histDir, itemName);
2311
+ const dest = join11(histDir, itemName);
2390
2312
  try {
2391
- await rename(join13(input.vault, w.path), dest);
2313
+ await rename(join11(input.vault, w.path), dest);
2392
2314
  archived.push(w.path);
2393
- } catch {
2315
+ } catch (error) {
2316
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: w.path, destination: dest, message: String(error) }) };
2394
2317
  }
2395
2318
  } else {
2396
- const dest = join13(archiveDir, w.path.replace(/\//g, "_"));
2319
+ const dest = join11(archiveDir, w.path.replace(/\//g, "_"));
2397
2320
  try {
2398
- await rename(join13(input.vault, w.path), dest);
2321
+ await rename(join11(input.vault, w.path), dest);
2399
2322
  archived.push(w.path);
2400
- } catch {
2323
+ } catch (error) {
2324
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: w.path, destination: dest, message: String(error) }) };
2401
2325
  }
2402
2326
  }
2403
2327
  }
@@ -2450,16 +2374,48 @@ async function runPagesize(input) {
2450
2374
  }
2451
2375
 
2452
2376
  // src/commands/log-rotate.ts
2453
- import { readFile as readFile10, rename as rename2, writeFile as writeFile2, stat as stat4 } from "fs/promises";
2454
- import { join as join14 } from "path";
2377
+ import { access, readFile as readFile10, rename as rename2, writeFile as writeFile2, stat as stat3 } from "fs/promises";
2378
+ import { join as join12 } from "path";
2455
2379
  var ENTRY_RE2 = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
2380
+ var FULL_DATE_RE = /^## \[(\d{4}-\d{2}-\d{2})\]/gm;
2381
+ async function pathExists(p) {
2382
+ try {
2383
+ await access(p);
2384
+ return true;
2385
+ } catch {
2386
+ return false;
2387
+ }
2388
+ }
2389
+ function archiveNameForExistingYearLog(existingText, fallbackYear) {
2390
+ const dates = [...existingText.matchAll(FULL_DATE_RE)].map((m) => m[1]);
2391
+ if (dates.length === 0) {
2392
+ return `log-archive-${fallbackYear}-unknown.md`;
2393
+ }
2394
+ const first = dates[0];
2395
+ const last = dates[dates.length - 1];
2396
+ const lastMd = last.slice(5);
2397
+ return `log-archive-${first}-to-${lastMd}.md`;
2398
+ }
2399
+ async function sidelineExistingYearLog(vault, rotatedPath, fallbackYear) {
2400
+ if (!await pathExists(rotatedPath)) return void 0;
2401
+ const existingText = await readFile10(rotatedPath, "utf8");
2402
+ let archiveName = archiveNameForExistingYearLog(existingText, fallbackYear);
2403
+ let archivePath = join12(vault, archiveName);
2404
+ if (await pathExists(archivePath)) {
2405
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2406
+ archiveName = archiveName.replace(/\.md$/, `-${stamp}.md`);
2407
+ archivePath = join12(vault, archiveName);
2408
+ }
2409
+ await rename2(rotatedPath, archivePath);
2410
+ return archiveName;
2411
+ }
2456
2412
  async function runLogRotate(input) {
2457
2413
  try {
2458
- await stat4(join14(input.vault, "SCHEMA.md"));
2414
+ await stat3(join12(input.vault, "SCHEMA.md"));
2459
2415
  } catch {
2460
2416
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
2461
2417
  }
2462
- const logPath = join14(input.vault, "log.md");
2418
+ const logPath = join12(input.vault, "log.md");
2463
2419
  let logText;
2464
2420
  try {
2465
2421
  logText = await readFile10(logPath, "utf8");
@@ -2471,37 +2427,56 @@ async function runLogRotate(input) {
2471
2427
  if (entries < input.threshold) {
2472
2428
  return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: false, humanHint: `${entries}/${input.threshold} entries \u2014 no rotation needed` }) };
2473
2429
  }
2430
+ const newestYear = matches[matches.length - 1][1];
2431
+ const rotatedName = `log-${newestYear}.md`;
2432
+ const rotatedPath = join12(input.vault, rotatedName);
2433
+ const yearExists = await pathExists(rotatedPath);
2474
2434
  if (!input.apply) {
2435
+ const hint = yearExists ? `${entries}/${input.threshold} entries \u2014 rotation needed (use --apply); existing ${rotatedName} will be sidelined to log-archive-* first` : `${entries}/${input.threshold} entries \u2014 rotation needed (use --apply)`;
2475
2436
  return {
2476
2437
  exitCode: ExitCode.LOG_ROTATE_NEEDED,
2477
- result: ok({ entries, threshold: input.threshold, rotated: false, humanHint: `${entries}/${input.threshold} entries \u2014 rotation needed (use --apply)` })
2438
+ result: ok({ entries, threshold: input.threshold, rotated: false, humanHint: hint })
2478
2439
  };
2479
2440
  }
2480
- const newestYear = matches[matches.length - 1][1];
2481
- const rotatedName = `log-${newestYear}.md`;
2482
- const rotatedPath = join14(input.vault, rotatedName);
2441
+ let archivedExistingTo;
2483
2442
  try {
2443
+ archivedExistingTo = await sidelineExistingYearLog(input.vault, rotatedPath, newestYear);
2484
2444
  await rename2(logPath, rotatedPath);
2485
2445
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2446
+ const sidelineNote = archivedExistingTo ? `
2447
+ - Prior ${rotatedName} preserved as ${archivedExistingTo}
2448
+ ` : "";
2486
2449
  const fresh = `# Vault Log
2487
2450
 
2488
2451
  Chronological action log. Newest entries last. Skill writes append entries; lint may rotate.
2489
2452
 
2490
2453
  ## [${today}] rotate | Log rotated from ${entries} entries
2491
2454
 
2492
- - Previous log moved to ${rotatedName}
2493
- `;
2455
+ - Previous log moved to ${rotatedName}${sidelineNote}`;
2494
2456
  await writeFile2(logPath, fresh, "utf8");
2495
2457
  } catch (e) {
2496
2458
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
2497
2459
  }
2460
+ const files = ["log.md", rotatedName];
2461
+ if (archivedExistingTo) files.push(archivedExistingTo);
2462
+ const summary = archivedExistingTo ? `rotated ${entries} entries to ${rotatedName}; archived existing to ${archivedExistingTo}` : `rotated ${entries} entries to ${rotatedName}`;
2498
2463
  appendLastOp(input.vault, {
2499
2464
  operation: "log-rotate",
2500
- summary: `rotated ${entries} entries to ${rotatedName}`,
2501
- files: ["log.md", rotatedName],
2465
+ summary,
2466
+ files,
2502
2467
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2503
2468
  });
2504
- return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: true, rotated_to: rotatedName, humanHint: `rotated ${entries} entries to ${rotatedName}` }) };
2469
+ return {
2470
+ exitCode: ExitCode.OK,
2471
+ result: ok({
2472
+ entries,
2473
+ threshold: input.threshold,
2474
+ rotated: true,
2475
+ rotated_to: rotatedName,
2476
+ archived_existing_to: archivedExistingTo,
2477
+ humanHint: summary
2478
+ })
2479
+ };
2505
2480
  }
2506
2481
 
2507
2482
  // src/commands/topic-map-check.ts
@@ -2525,12 +2500,12 @@ async function runTopicMapCheck(input) {
2525
2500
 
2526
2501
  // src/commands/index-link-format.ts
2527
2502
  import { readFile as readFile11 } from "fs/promises";
2528
- import { join as join15 } from "path";
2503
+ import { join as join13 } from "path";
2529
2504
  var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
2530
2505
  async function runIndexLinkFormat(input) {
2531
2506
  let text = "";
2532
2507
  try {
2533
- text = await readFile11(join15(input.vault, "index.md"), "utf8");
2508
+ text = await readFile11(join13(input.vault, "index.md"), "utf8");
2534
2509
  } catch {
2535
2510
  }
2536
2511
  const markdown_links = [];
@@ -2543,9 +2518,9 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
2543
2518
  }
2544
2519
 
2545
2520
  // src/commands/dedup.ts
2546
- import { createHash as createHash3 } from "crypto";
2547
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
2548
- import { dirname as dirname4, join as join16, resolve as resolve3 } from "path";
2521
+ import { createHash as createHash5 } from "crypto";
2522
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
2523
+ import { dirname as dirname5, join as join14, posix, resolve as resolve3 } from "path";
2549
2524
 
2550
2525
  // src/utils/rclone.ts
2551
2526
  import { execFile } from "child_process";
@@ -2594,6 +2569,140 @@ async function defaultRcloneRunner(args) {
2594
2569
  }
2595
2570
  }
2596
2571
 
2572
+ // src/utils/safe-write.ts
2573
+ import { readFile as readFile12, writeFile as writeFile3 } from "fs/promises";
2574
+ var DEFAULT_MIN_BODY_RATIO = 0.5;
2575
+ var DEFAULT_MIN_OLD_BODY_BYTES = 200;
2576
+ function bodyBytes(text) {
2577
+ const split = splitFrontmatter(text);
2578
+ if (!split.ok) return Buffer.byteLength(text, "utf8");
2579
+ return Buffer.byteLength(split.data.body, "utf8");
2580
+ }
2581
+ async function readIfExists(absPath) {
2582
+ try {
2583
+ return await readFile12(absPath, "utf8");
2584
+ } catch (e) {
2585
+ if (e.code === "ENOENT") return null;
2586
+ throw e;
2587
+ }
2588
+ }
2589
+ async function safeWritePage(absPath, newContent, opts = {}) {
2590
+ const minRatio = opts.minBodyRatio === void 0 ? DEFAULT_MIN_BODY_RATIO : opts.minBodyRatio;
2591
+ const minOldBytes = opts.minOldBodyBytes ?? DEFAULT_MIN_OLD_BODY_BYTES;
2592
+ let oldContent;
2593
+ try {
2594
+ oldContent = await readIfExists(absPath);
2595
+ } catch (e) {
2596
+ return err("WRITE_FAILED", { path: absPath, phase: "read-existing", message: String(e) });
2597
+ }
2598
+ const isNew = oldContent === null;
2599
+ const oldBodyBytes = isNew ? 0 : bodyBytes(oldContent);
2600
+ const newBodyBytes = bodyBytes(newContent);
2601
+ const bodyRatio = oldBodyBytes > 0 ? newBodyBytes / oldBodyBytes : null;
2602
+ let guardSkippedSmall = false;
2603
+ if (!isNew && minRatio !== null && bodyRatio !== null && bodyRatio < minRatio) {
2604
+ if (oldBodyBytes < minOldBytes) {
2605
+ guardSkippedSmall = true;
2606
+ } else {
2607
+ return err("BODY_TRUNCATION_GUARD", {
2608
+ path: absPath,
2609
+ oldBodyBytes,
2610
+ newBodyBytes,
2611
+ bodyRatio,
2612
+ minBodyRatio: minRatio,
2613
+ hint: "Refusing to write \u2014 new body lost too much content. Likely a parse-modify-serialize bug or a write race. Verify the page source before retrying."
2614
+ });
2615
+ }
2616
+ }
2617
+ const written = await atomicWriteText(absPath, newContent);
2618
+ if (!written.ok) return written;
2619
+ return ok({
2620
+ changed: written.data.changed,
2621
+ isNew,
2622
+ oldBodyBytes,
2623
+ newBodyBytes,
2624
+ bodyRatio,
2625
+ guardSkippedSmall
2626
+ });
2627
+ }
2628
+
2629
+ // src/utils/raw-reference-rewrite.ts
2630
+ function withoutMarkdownExtension(path) {
2631
+ return path.endsWith(".md") ? path.slice(0, -3) : path;
2632
+ }
2633
+ function replacementFor(value, oldPath, newPath) {
2634
+ const oldNoExtension = withoutMarkdownExtension(oldPath);
2635
+ const newNoExtension = withoutMarkdownExtension(newPath);
2636
+ if (value === oldPath) return newPath;
2637
+ if (value === oldNoExtension) return newNoExtension;
2638
+ if (value === `^[${oldPath}]`) return `^[${newPath}]`;
2639
+ if (value === `^[${oldNoExtension}]`) return `^[${newNoExtension}]`;
2640
+ return null;
2641
+ }
2642
+ function renderSources(values) {
2643
+ if (values.length === 0) return "sources: []";
2644
+ return `sources:
2645
+ ${values.map((value) => ` - ${value}`).join("\n")}`;
2646
+ }
2647
+ function rewriteRawSourceReferences(text, oldPath, newPath) {
2648
+ const split = splitFrontmatter(text);
2649
+ const oldNoExtension = withoutMarkdownExtension(oldPath);
2650
+ const newNoExtension = withoutMarkdownExtension(newPath);
2651
+ const body = split.ok ? split.data.body : text;
2652
+ let rewrittenBody = body;
2653
+ let bodyCitationCount = 0;
2654
+ for (const [oldMarker, newMarker] of [
2655
+ [`^[${oldPath}]`, `^[${newPath}]`],
2656
+ [`^[${oldNoExtension}]`, `^[${newNoExtension}]`]
2657
+ ]) {
2658
+ const count = rewrittenBody.split(oldMarker).length - 1;
2659
+ if (count > 0) {
2660
+ rewrittenBody = rewrittenBody.replaceAll(oldMarker, newMarker);
2661
+ bodyCitationCount += count;
2662
+ }
2663
+ }
2664
+ if (!split.ok) {
2665
+ return { text: rewrittenBody, changed: rewrittenBody !== text, sourcesBefore: [], sourcesAfter: [], bodyCitationCount };
2666
+ }
2667
+ const frontmatter = extractFrontmatter(text);
2668
+ const sourcesBefore = frontmatter.ok && Array.isArray(frontmatter.data.sources) ? frontmatter.data.sources.filter((value) => typeof value === "string") : [];
2669
+ const sourcesAfter = sourcesBefore.map((value) => replacementFor(value, oldPath, newPath) ?? value);
2670
+ const sourcesChanged = sourcesBefore.some((value, index) => value !== sourcesAfter[index]);
2671
+ let rewrittenFrontmatter = split.data.rawFrontmatter;
2672
+ if (sourcesChanged) {
2673
+ rewrittenFrontmatter = rewrittenFrontmatter.replace(
2674
+ /^sources:\s*(?:\[[^\]]*\]|(?:\r?\n(?:\s*-\s.*))+)/m,
2675
+ renderSources(sourcesAfter)
2676
+ );
2677
+ }
2678
+ if (!sourcesChanged && rewrittenBody === body) {
2679
+ return { text, changed: false, sourcesBefore, sourcesAfter, bodyCitationCount };
2680
+ }
2681
+ const rewritten = `---
2682
+ ${rewrittenFrontmatter}
2683
+ ---${rewrittenBody}`;
2684
+ return {
2685
+ text: rewritten,
2686
+ changed: rewritten !== text,
2687
+ sourcesBefore,
2688
+ sourcesAfter,
2689
+ bodyCitationCount
2690
+ };
2691
+ }
2692
+
2693
+ // src/utils/maintained-page-state.ts
2694
+ import { createHash as createHash4 } from "crypto";
2695
+ import { readFile as readFile13 } from "fs/promises";
2696
+ async function snapshotMaintainedPageState(pages) {
2697
+ const entries = await Promise.all(
2698
+ pages.filter((page) => !page.relPath.startsWith("raw/")).map(async (page) => {
2699
+ const bytes = await readFile13(page.absPath);
2700
+ return `${page.relPath}:${createHash4("sha256").update(bytes).digest("hex")}`;
2701
+ })
2702
+ );
2703
+ return entries.sort();
2704
+ }
2705
+
2597
2706
  // src/commands/dedup.ts
2598
2707
  async function runDedup(input) {
2599
2708
  if (input.canonicalPolicy && input.canonicalPolicy !== "scan-order" && input.canonicalPolicy !== "stable-path") {
@@ -2614,7 +2723,11 @@ async function runDedup(input) {
2614
2723
  }
2615
2724
  const hashMap = /* @__PURE__ */ new Map();
2616
2725
  let totalFiles = 0;
2617
- const rawEntries = await mapWithConcurrency(scan.raw, vaultIoConcurrency(), async (raw) => {
2726
+ const activeRaw = scan.raw.filter((raw) => {
2727
+ const classified = classifyRawPath(raw.relPath);
2728
+ return classified.ok && classified.data.storage === "active" && classified.data.category !== "assets";
2729
+ });
2730
+ const rawEntries = await mapWithConcurrency(activeRaw, vaultIoConcurrency(), async (raw) => {
2618
2731
  const fm = extractFrontmatter(await readPageCached(raw, input.pageTextCache));
2619
2732
  if (!fm.ok) return null;
2620
2733
  const sha = typeof fm.data.sha256 === "string" ? fm.data.sha256 : null;
@@ -2629,77 +2742,101 @@ async function runDedup(input) {
2629
2742
  else hashMap.set(entry.sha, [entry.relPath]);
2630
2743
  }
2631
2744
  const canonicalPolicy = input.canonicalPolicy ?? "stable-path";
2632
- const duplicates = [...hashMap.entries()].filter(([, files]) => files.length > 1).map(([sha256, files]) => ({ sha256, files: orderFiles(files, canonicalPolicy) }));
2745
+ const duplicates = [...hashMap.entries()].filter(([, files]) => files.length > 1).map(([sha2562, files]) => ({ sha256: sha2562, files: orderFiles(files, canonicalPolicy) }));
2633
2746
  const rewired = [];
2634
2747
  const removed = [];
2748
+ const relocated = [];
2635
2749
  const unsafe = [];
2636
2750
  const safeEntries = manifestFromFile?.ok ? manifestFromFile.data.entries : buildSafeEntries(input.vault, duplicates, unsafe);
2751
+ if (manifestFromFile?.ok) {
2752
+ const validatedManifest = validateManifestLocalState(input.vault, manifestFromFile.data);
2753
+ if (!validatedManifest.ok) return { exitCode: ExitCode.USAGE, result: validatedManifest };
2754
+ }
2637
2755
  const manifest = safeEntries.length > 0 ? {
2638
2756
  version: 1,
2639
2757
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2640
2758
  vault: resolve3(input.vault),
2641
2759
  entries: safeEntries
2642
2760
  } : void 0;
2643
- const remote = await planAndMaybePruneRemote(input, safeEntries);
2644
- if (!remote.ok) {
2645
- return {
2646
- exitCode: remote.error === "SYNC_PUSH_FAILED" ? ExitCode.SYNC_PUSH_FAILED : ExitCode.USAGE,
2647
- result: remote
2648
- };
2761
+ const remoteDeleteCount = safeEntries.reduce((count, entry) => count + entry.duplicates.length, 0);
2762
+ if (input.remoteDelete && (!Number.isInteger(input.maxRemoteDeletes) || (input.maxRemoteDeletes ?? 0) < remoteDeleteCount)) {
2763
+ return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: `planned remote deletes ${remoteDeleteCount} exceed --max-remote-deletes ${input.maxRemoteDeletes ?? 0}` }) };
2764
+ }
2765
+ const structuralPlans = [];
2766
+ for (const entry of safeEntries) {
2767
+ for (const duplicate of entry.duplicates) {
2768
+ if (!existsSync4(join14(input.vault, duplicate))) continue;
2769
+ const destination = dedupDestination(input.vault, duplicate);
2770
+ const plan = await planRawStructuralMove({ vault: input.vault, operation: "deduplicate", source: duplicate, destination });
2771
+ if (!plan.ok) return { exitCode: ExitCode.WRITE_FAILED, result: plan };
2772
+ structuralPlans.push({ source: duplicate, destination, canonical: entry.canonical, approval: plan.data.approval_token, source_sha256: plan.data.source_sha256 });
2773
+ }
2774
+ }
2775
+ const citationState = await snapshotMaintainedPageState(scan.allMarkdown);
2776
+ const approvalToken = structuralPlans.length > 0 ? operationId("raw-dedup-approval", [
2777
+ ...structuralPlans.flatMap((plan) => [plan.source, plan.destination, plan.source_sha256]),
2778
+ ...citationState
2779
+ ]) : void 0;
2780
+ if (input.apply && structuralPlans.length > 0 && input.approve !== approvalToken) {
2781
+ return { exitCode: ExitCode.USAGE, result: err("APPROVAL_INVALID", { message: "dedup apply requires the live dry-run --approve token", approval_token: approvalToken }) };
2649
2782
  }
2650
2783
  if (input.manifestOut && manifest) {
2651
2784
  try {
2652
- mkdirSync4(dirname4(input.manifestOut), { recursive: true });
2785
+ mkdirSync4(dirname5(input.manifestOut), { recursive: true });
2653
2786
  writeFileSync4(input.manifestOut, `${JSON.stringify(manifest, null, 2)}
2654
2787
  `, "utf-8");
2655
2788
  } catch (e) {
2656
2789
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: input.manifestOut, message: String(e) }) };
2657
2790
  }
2658
2791
  }
2659
- if (input.apply && safeEntries.length > 0) {
2792
+ if (input.apply && structuralPlans.length > 0) {
2660
2793
  const replacements = /* @__PURE__ */ new Map();
2661
- for (const entry of safeEntries) {
2662
- for (const duplicate of entry.duplicates) {
2663
- replacements.set(duplicate, entry.canonical);
2664
- }
2665
- }
2794
+ for (const plan of structuralPlans) replacements.set(plan.source, plan.canonical);
2795
+ const pendingWrites = [];
2666
2796
  for (const page of scan.allMarkdown.filter((p) => !p.relPath.startsWith("raw/"))) {
2667
- const text = readFileSync4(join16(input.vault, page.relPath), "utf-8");
2797
+ const text = readFileSync4(join14(input.vault, page.relPath), "utf-8");
2668
2798
  let updated = text;
2669
2799
  let changed = false;
2670
2800
  for (const [oldPath, newPath] of replacements) {
2671
- const oldMarker = `^[${oldPath}]`;
2672
- const newMarker = `^[${newPath}]`;
2673
- if (updated.includes(oldMarker)) {
2674
- updated = updated.replaceAll(oldMarker, newMarker);
2675
- changed = true;
2676
- }
2677
- const oldFm = `- "^[${oldPath}]"`;
2678
- const newFm = `- "^[${newPath}]"`;
2679
- if (updated.includes(oldFm)) {
2680
- updated = updated.replaceAll(oldFm, newFm);
2681
- changed = true;
2682
- }
2801
+ const rewritten = rewriteRawSourceReferences(updated, oldPath, newPath);
2802
+ updated = rewritten.text;
2803
+ changed = changed || rewritten.changed;
2683
2804
  }
2684
2805
  if (changed) {
2685
- writeFileSync4(join16(input.vault, page.relPath), updated);
2806
+ pendingWrites.push({ page: page.relPath, text: updated });
2686
2807
  rewired.push(page.relPath);
2687
2808
  }
2688
2809
  }
2689
- for (const oldPath of replacements.keys()) {
2690
- const fullPath = join16(input.vault, oldPath);
2691
- try {
2692
- unlinkSync4(fullPath);
2693
- removed.push(oldPath);
2694
- } catch {
2695
- }
2810
+ for (const plan of structuralPlans) {
2811
+ const moved = await applyRawStructuralMove({
2812
+ vault: input.vault,
2813
+ operation: "deduplicate",
2814
+ source: plan.source,
2815
+ destination: plan.destination,
2816
+ approve: plan.approval,
2817
+ command: "skillwiki dedup --apply",
2818
+ citationChanges: pendingWrites.map((write) => write.page)
2819
+ });
2820
+ if (!moved.ok) return { exitCode: ExitCode.WRITE_FAILED, result: moved };
2821
+ relocated.push({ from: plan.source, to: plan.destination });
2822
+ }
2823
+ for (const pending of pendingWrites) {
2824
+ const write = await safeWritePage(join14(input.vault, pending.page), pending.text);
2825
+ if (!write.ok) return { exitCode: ExitCode.WRITE_FAILED, result: write };
2696
2826
  }
2697
2827
  }
2698
- if (input.apply && (rewired.length > 0 || removed.length > 0)) {
2828
+ const remote = await planAndMaybePruneRemote({ ...input, remoteDelete: input.apply || input.manifestIn ? input.remoteDelete : false }, safeEntries);
2829
+ if (!remote.ok) {
2830
+ return {
2831
+ exitCode: remote.error === "SYNC_PUSH_FAILED" ? ExitCode.SYNC_PUSH_FAILED : ExitCode.USAGE,
2832
+ result: remote
2833
+ };
2834
+ }
2835
+ if (input.apply && (rewired.length > 0 || relocated.length > 0)) {
2699
2836
  appendLastOp(input.vault, {
2700
2837
  operation: "dedup",
2701
- summary: `rewired ${rewired.length} pages, removed ${removed.length} duplicates`,
2702
- files: [...rewired, ...removed],
2838
+ summary: `rewired ${rewired.length} pages, preserved ${relocated.length} duplicates under raw/duplicates`,
2839
+ files: [...rewired, ...relocated.flatMap((move) => [move.from, move.to])],
2703
2840
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2704
2841
  });
2705
2842
  }
@@ -2710,7 +2847,7 @@ async function runDedup(input) {
2710
2847
  for (const d of duplicates) hintLines.push(` ${d.sha256.slice(0, 12)}... \u2192 ${d.files.join(", ")}`);
2711
2848
  if (input.apply) {
2712
2849
  hintLines.push(`rewired: ${rewired.length} pages`);
2713
- hintLines.push(`removed: ${removed.length} raw files`);
2850
+ hintLines.push(`preserved duplicates: ${relocated.length} raw files`);
2714
2851
  }
2715
2852
  if (unsafe.length > 0) hintLines.push(`unsafe: ${unsafe.length} groups skipped`);
2716
2853
  if (remote.data.plannedDeletes.length > 0) {
@@ -2730,10 +2867,21 @@ async function runDedup(input) {
2730
2867
  remote: remote.data,
2731
2868
  rewired,
2732
2869
  removed,
2870
+ relocated,
2871
+ ...approvalToken ? { approval_token: approvalToken } : {},
2733
2872
  humanHint: hintLines.join("\n")
2734
2873
  })
2735
2874
  };
2736
2875
  }
2876
+ function dedupDestination(vault, source) {
2877
+ const base = lifecycleDestination(source, "deduplicate");
2878
+ if (!base.ok) throw new Error(`unsupported raw dedup path: ${source}`);
2879
+ if (!existsSync4(join14(vault, base.data))) return base.data;
2880
+ const contentHash = createHash5("sha256").update(readFileSync4(join14(vault, source))).digest("hex").slice(0, 8);
2881
+ const ext = posix.extname(base.data);
2882
+ const stem = ext ? base.data.slice(0, -ext.length) : base.data;
2883
+ return `${stem}--${contentHash}${ext}`;
2884
+ }
2737
2885
  function orderFiles(files, policy) {
2738
2886
  if (policy === "scan-order") return [...files];
2739
2887
  return [...files].sort(compareStableRawPath);
@@ -2781,10 +2929,13 @@ function buildSafeEntries(vault, duplicates, unsafe) {
2781
2929
  return entries;
2782
2930
  }
2783
2931
  function hashRawBody(vault, relPath) {
2784
- const text = readFileSync4(join16(vault, relPath), "utf-8");
2932
+ const text = readFileSync4(join14(vault, relPath), "utf-8");
2933
+ return hashRawBodyText(text);
2934
+ }
2935
+ function hashRawBodyText(text) {
2785
2936
  const split = splitFrontmatter(text);
2786
2937
  const body = split.ok ? split.data.body : text;
2787
- return createHash3("sha256").update(body).digest("hex");
2938
+ return createHash5("sha256").update(body).digest("hex");
2788
2939
  }
2789
2940
  function readManifest(path) {
2790
2941
  try {
@@ -2797,69 +2948,50 @@ function readManifest(path) {
2797
2948
  return err("INVALID_FRONTMATTER", { path, message: String(e) });
2798
2949
  }
2799
2950
  }
2951
+ function validateManifestLocalState(vault, manifest) {
2952
+ if (resolve3(manifest.vault) !== resolve3(vault)) {
2953
+ return err("APPROVAL_INVALID", {
2954
+ message: "dedup manifest belongs to a different vault",
2955
+ manifest_vault: manifest.vault,
2956
+ vault: resolve3(vault)
2957
+ });
2958
+ }
2959
+ for (const entry of manifest.entries) {
2960
+ if (!/^[a-f0-9]{64}$/i.test(entry.sha256) || !/^[a-f0-9]{64}$/i.test(entry.bodyHash)) {
2961
+ return err("APPROVAL_INVALID", { message: "dedup manifest contains an invalid hash", canonical: entry.canonical });
2962
+ }
2963
+ const paths = [entry.canonical, ...entry.duplicates];
2964
+ if (new Set(paths).size !== paths.length) {
2965
+ return err("APPROVAL_INVALID", { message: "dedup manifest contains repeated or self-duplicate paths", canonical: entry.canonical });
2966
+ }
2967
+ for (const path of paths) {
2968
+ const classified = typeof path === "string" ? classifyRawPath(path) : err("RAW_PATH_INVALID", { path });
2969
+ if (!classified.ok || classified.data.path !== path || classified.data.storage !== "active" || classified.data.category === "assets") {
2970
+ return err("APPROVAL_INVALID", { message: "dedup manifest contains an invalid active raw source path", path });
2971
+ }
2972
+ }
2973
+ const canonicalExists = existsSync4(join14(vault, entry.canonical));
2974
+ const liveDuplicates = entry.duplicates.filter((path) => existsSync4(join14(vault, path)));
2975
+ if (!canonicalExists && liveDuplicates.length > 0) {
2976
+ return err("APPROVAL_INVALID", { message: "dedup manifest canonical is missing while a local duplicate exists", canonical: entry.canonical });
2977
+ }
2978
+ for (const path of canonicalExists ? [entry.canonical, ...liveDuplicates] : liveDuplicates) {
2979
+ const text = readFileSync4(join14(vault, path), "utf8");
2980
+ const fm = extractFrontmatter(text);
2981
+ const declaredSha = fm.ok && typeof fm.data.sha256 === "string" ? fm.data.sha256 : null;
2982
+ if (declaredSha !== entry.sha256 || hashRawBodyText(text) !== entry.bodyHash) {
2983
+ return err("APPROVAL_INVALID", { message: "dedup manifest does not match live local raw state", path });
2984
+ }
2985
+ }
2986
+ }
2987
+ return ok(true);
2988
+ }
2800
2989
  async function planAndMaybePruneRemote(input, entries) {
2801
2990
  const remoteRoot = normalizeRemoteRoot(input.remote);
2802
2991
  const plannedDeletes = remoteRoot ? entries.flatMap((entry) => entry.duplicates.map((path) => `${remoteRoot}/${path}`)) : [];
2803
2992
  return planAndMaybePruneRemoteObjects(plannedDeletes, { ...input, defaultMaxDeletes: 50 });
2804
2993
  }
2805
2994
 
2806
- // src/utils/safe-write.ts
2807
- import { readFile as readFile12, writeFile as writeFile3 } from "fs/promises";
2808
- var DEFAULT_MIN_BODY_RATIO = 0.5;
2809
- var DEFAULT_MIN_OLD_BODY_BYTES = 200;
2810
- function bodyBytes(text) {
2811
- const split = splitFrontmatter(text);
2812
- if (!split.ok) return Buffer.byteLength(text, "utf8");
2813
- return Buffer.byteLength(split.data.body, "utf8");
2814
- }
2815
- async function readIfExists(absPath) {
2816
- try {
2817
- return await readFile12(absPath, "utf8");
2818
- } catch (e) {
2819
- if (e.code === "ENOENT") return null;
2820
- throw e;
2821
- }
2822
- }
2823
- async function safeWritePage(absPath, newContent, opts = {}) {
2824
- const minRatio = opts.minBodyRatio === void 0 ? DEFAULT_MIN_BODY_RATIO : opts.minBodyRatio;
2825
- const minOldBytes = opts.minOldBodyBytes ?? DEFAULT_MIN_OLD_BODY_BYTES;
2826
- let oldContent;
2827
- try {
2828
- oldContent = await readIfExists(absPath);
2829
- } catch (e) {
2830
- return err("WRITE_FAILED", { path: absPath, phase: "read-existing", message: String(e) });
2831
- }
2832
- const isNew = oldContent === null;
2833
- const oldBodyBytes = isNew ? 0 : bodyBytes(oldContent);
2834
- const newBodyBytes = bodyBytes(newContent);
2835
- const bodyRatio = oldBodyBytes > 0 ? newBodyBytes / oldBodyBytes : null;
2836
- let guardSkippedSmall = false;
2837
- if (!isNew && minRatio !== null && bodyRatio !== null && bodyRatio < minRatio) {
2838
- if (oldBodyBytes < minOldBytes) {
2839
- guardSkippedSmall = true;
2840
- } else {
2841
- return err("BODY_TRUNCATION_GUARD", {
2842
- path: absPath,
2843
- oldBodyBytes,
2844
- newBodyBytes,
2845
- bodyRatio,
2846
- minBodyRatio: minRatio,
2847
- hint: "Refusing to write \u2014 new body lost too much content. Likely a parse-modify-serialize bug or a write race. Verify the page source before retrying."
2848
- });
2849
- }
2850
- }
2851
- const written = await atomicWriteText(absPath, newContent);
2852
- if (!written.ok) return written;
2853
- return ok({
2854
- changed: written.data.changed,
2855
- isNew,
2856
- oldBodyBytes,
2857
- newBodyBytes,
2858
- bodyRatio,
2859
- guardSkippedSmall
2860
- });
2861
- }
2862
-
2863
2995
  // src/commands/frontmatter-fix.ts
2864
2996
  function isoToday() {
2865
2997
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -2938,9 +3070,8 @@ ${newBody}`;
2938
3070
 
2939
3071
  // src/commands/lint.ts
2940
3072
  import { existsSync as existsSync6 } from "fs";
2941
- import { readFile as readFile14, readdir as readdir4 } from "fs/promises";
2942
- import { createHash as createHash5 } from "crypto";
2943
- import { join as join18, relative as relative2, sep as sep2 } from "path";
3073
+ import { readFile as readFile15, readdir as readdir3 } from "fs/promises";
3074
+ import { join as join16, relative as relative2, sep as sep2 } from "path";
2944
3075
 
2945
3076
  // src/commands/sparse-community.ts
2946
3077
  async function runSparseCommunity(input) {
@@ -2956,7 +3087,7 @@ async function runSparseCommunity(input) {
2956
3087
  }
2957
3088
 
2958
3089
  // src/commands/raw-body-dedup.ts
2959
- import { createHash as createHash4 } from "crypto";
3090
+ import { createHash as createHash6 } from "crypto";
2960
3091
  async function runRawBodyDedup(vault, scan, pageTextCache) {
2961
3092
  const scanResult = scan ? ok(scan) : await scanVault(vault);
2962
3093
  if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
@@ -2965,7 +3096,7 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2965
3096
  const text = await readPageCached(raw, pageTextCache);
2966
3097
  const split = splitFrontmatter(text);
2967
3098
  if (!split.ok) return null;
2968
- const bodyHash = createHash4("sha256").update(split.data.body).digest("hex");
3099
+ const bodyHash = createHash6("sha256").update(split.data.body).digest("hex");
2969
3100
  const fm = extractFrontmatter(text);
2970
3101
  let fmSha256 = null;
2971
3102
  if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
@@ -2998,8 +3129,8 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2998
3129
 
2999
3130
  // src/commands/path-too-long.ts
3000
3131
  import { existsSync as existsSync5 } from "fs";
3001
- import { mkdir as mkdir5, readFile as readFile13, rename as rename3, unlink } from "fs/promises";
3002
- import { dirname as dirname5, join as join17, posix, resolve as resolve4 } from "path";
3132
+ import { mkdir as mkdir5, readFile as readFile14, rename as rename3, unlink as unlink2 } from "fs/promises";
3133
+ import { dirname as dirname6, join as join15, posix as posix2, resolve as resolve4 } from "path";
3003
3134
  var MAX_PATH_LENGTH = 240;
3004
3135
  var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
3005
3136
  async function runPathTooLong(input) {
@@ -3025,6 +3156,10 @@ async function fixPathTooLong(input) {
3025
3156
  const fixed = [];
3026
3157
  const unresolved = [];
3027
3158
  for (const violation of violations) {
3159
+ if (violation.relPath.startsWith("raw/")) {
3160
+ unresolved.push(violation.relPath);
3161
+ continue;
3162
+ }
3028
3163
  const target = await resolveFixTarget(input.vault, violation.relPath, violation.suggestedRelPath, maxFixLength);
3029
3164
  if (!target || target.relPath === violation.relPath || target.relPath.length > maxFixLength) {
3030
3165
  unresolved.push(violation.relPath);
@@ -3032,10 +3167,10 @@ async function fixPathTooLong(input) {
3032
3167
  }
3033
3168
  try {
3034
3169
  if (target.mode === "dedupe") {
3035
- await unlink(join17(input.vault, violation.relPath));
3170
+ await unlink2(join15(input.vault, violation.relPath));
3036
3171
  } else {
3037
- await mkdir5(dirname5(join17(input.vault, target.relPath)), { recursive: true });
3038
- await rename3(join17(input.vault, violation.relPath), join17(input.vault, target.relPath));
3172
+ await mkdir5(dirname6(join15(input.vault, target.relPath)), { recursive: true });
3173
+ await rename3(join15(input.vault, violation.relPath), join15(input.vault, target.relPath));
3039
3174
  }
3040
3175
  fixed.push({ from: violation.relPath, to: target.relPath });
3041
3176
  } catch {
@@ -3049,7 +3184,7 @@ async function fixPathTooLong(input) {
3049
3184
  for (const page of afterScan.data.allMarkdown) {
3050
3185
  if (!shouldRewriteReferences(page.relPath)) continue;
3051
3186
  try {
3052
- const original = await readFile13(page.absPath, "utf8");
3187
+ const original = await readFile14(page.absPath, "utf8");
3053
3188
  let updated = original;
3054
3189
  for (const fix of fixed) {
3055
3190
  updated = replacePathReferences(updated, fix.from, fix.to);
@@ -3112,9 +3247,9 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
3112
3247
  async function resolveFixTarget(vault, original, preferred, maxLength) {
3113
3248
  for (const candidate of candidateRelPaths(preferred, maxLength)) {
3114
3249
  if (candidate === original || candidate.length > maxLength) continue;
3115
- const candidatePath = join17(vault, candidate);
3250
+ const candidatePath = join15(vault, candidate);
3116
3251
  if (!existsSync5(candidatePath)) return { relPath: candidate, mode: "rename" };
3117
- if (await hasSameContent(join17(vault, original), candidatePath)) {
3252
+ if (await hasSameContent(join15(vault, original), candidatePath)) {
3118
3253
  return { relPath: candidate, mode: "dedupe" };
3119
3254
  }
3120
3255
  }
@@ -3123,8 +3258,8 @@ async function resolveFixTarget(vault, original, preferred, maxLength) {
3123
3258
  function candidateRelPaths(preferred, maxLength) {
3124
3259
  const candidates = [preferred];
3125
3260
  if (preferred.length > maxLength) return candidates;
3126
- const dir = posix.dirname(preferred) === "." ? "" : posix.dirname(preferred);
3127
- const filename = posix.basename(preferred);
3261
+ const dir = posix2.dirname(preferred) === "." ? "" : posix2.dirname(preferred);
3262
+ const filename = posix2.basename(preferred);
3128
3263
  const ext = filename.endsWith(".md") ? ".md" : "";
3129
3264
  const base = ext ? filename.slice(0, -3) : filename;
3130
3265
  const dirPrefix = dir ? `${dir}/` : "";
@@ -3138,7 +3273,7 @@ function candidateRelPaths(preferred, maxLength) {
3138
3273
  }
3139
3274
  async function hasSameContent(a, b) {
3140
3275
  try {
3141
- const [left, right] = await Promise.all([readFile13(a), readFile13(b)]);
3276
+ const [left, right] = await Promise.all([readFile14(a), readFile14(b)]);
3142
3277
  return left.equals(right);
3143
3278
  } catch {
3144
3279
  return false;
@@ -3151,8 +3286,8 @@ function shouldRewriteReferences(relPath) {
3151
3286
  }
3152
3287
  function replacePathReferences(content, oldRelPath, newRelPath) {
3153
3288
  let updated = content.replaceAll(oldRelPath, newRelPath);
3154
- const oldStem = posix.basename(oldRelPath).replace(/\.md$/, "");
3155
- const newStem = posix.basename(newRelPath).replace(/\.md$/, "");
3289
+ const oldStem = posix2.basename(oldRelPath).replace(/\.md$/, "");
3290
+ const newStem = posix2.basename(newRelPath).replace(/\.md$/, "");
3156
3291
  if (oldStem !== newStem) {
3157
3292
  const oldStemEscaped = oldStem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3158
3293
  const stemWikilinkRe = new RegExp(`\\[\\[${oldStemEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
@@ -3181,7 +3316,8 @@ function buildCliSurface() {
3181
3316
  program.command("graph");
3182
3317
  program.command("canvas");
3183
3318
  program.command("overlap").option("--wiki <name>");
3184
- program.command("query").option("--limit <n>").option("--wiki <name>");
3319
+ program.command("query").option("--limit <n>").option("--include-pending").option("--wiki <name>");
3320
+ program.command("sources");
3185
3321
  program.command("orphans").option("--wiki <name>");
3186
3322
  program.command("audit");
3187
3323
  program.command("install").option("--target <dir>").option("--dry-run").option("--skills-root <dir>").option("--symlink");
@@ -3193,7 +3329,7 @@ function buildCliSurface() {
3193
3329
  program.command("index-check").option("--wiki <name>");
3194
3330
  program.command("index-link-format").option("--wiki <name>");
3195
3331
  program.command("topic-map-check").option("--threshold <n>").option("--wiki <name>");
3196
- program.command("stale").option("--archive").option("--days <n>").option("--force-scan").option("--project <slug>").option("--wiki <name>");
3332
+ program.command("stale").option("--archive").option("--apply").option("--approve <token>").option("--days <n>").option("--force-scan").option("--project <slug>").option("--wiki <name>");
3197
3333
  program.command("claim").option("--project <slug>").option("--slug <slug>").option("--wiki <name>");
3198
3334
  program.command("pagesize").option("--lines <n>").option("--wiki <name>");
3199
3335
  program.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
@@ -3208,10 +3344,10 @@ function buildCliSurface() {
3208
3344
  program.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
3209
3345
  program.command("doctor").option("--check-snapshotter");
3210
3346
  program.command("status").option("--wiki <name>");
3211
- program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
3347
+ program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--approve <token>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
3212
3348
  program.command("remove").option("--wiki <name>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--reason <text>");
3213
3349
  program.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
3214
- program.command("dedup").option("--apply").option("--canonical-policy <policy>").option("--manifest-out <path>").option("--manifest-in <path>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--wiki <name>");
3350
+ program.command("dedup").option("--apply").option("--approve <token>").option("--canonical-policy <policy>").option("--manifest-out <path>").option("--manifest-in <path>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--wiki <name>");
3215
3351
  program.command("migrate-citations").option("--dry-run").option("--wiki <name>");
3216
3352
  program.command("frontmatter-fix").option("--dry-run").option("--wiki <name>");
3217
3353
  program.command("update").option("--tag <tag>");
@@ -3236,6 +3372,10 @@ function buildCliSurface() {
3236
3372
  program.command("mcp");
3237
3373
  const graphCmd = program.commands.find((c) => c.name() === "graph");
3238
3374
  graphCmd.command("build").option("--out <path>").option("--wiki <name>");
3375
+ const sourcesCmd = program.commands.find((c) => c.name() === "sources");
3376
+ sourcesCmd.command("pending").option("--since <date>").option("--older-than <days>").option("--match <text>").option("--ingested-by <channel>").option("--scope <scope>").option("--sort <order>").option("--limit <n>").option("--all").option("--include-integrated").option("--include-archived").option("--include-duplicates").option("--include-legacy-archived").option("--wiki <name>");
3377
+ sourcesCmd.command("disposition").requiredOption("--status <status>").requiredOption("--reason <text>").option("--review-after <date>").option("--duplicate-of <raw-path>").option("--write").option("--approve <token>").option("--wiki <name>");
3378
+ sourcesCmd.command("dispose").requiredOption("--reason <text>").option("--write").option("--approve <token>").option("--wiki <name>");
3239
3379
  const canvasCmd = program.commands.find((c) => c.name() === "canvas");
3240
3380
  canvasCmd.command("generate").option("--graph-path <path>").option("--wiki <name>");
3241
3381
  const configCmd = program.commands.find((c) => c.name() === "config");
@@ -3576,17 +3716,6 @@ function readMirrorHintLines(vault) {
3576
3716
  "if results look stale, refresh the read mirror or rerun with SKILLWIKI_DISABLE_VAULT_READ_MIRROR=1 for a live scan; live scans may be slower"
3577
3717
  ];
3578
3718
  }
3579
- function recomputeRawSha256IfPresent(content) {
3580
- const split = splitFrontmatter(content);
3581
- if (!split.ok) return content;
3582
- if (!/^sha256:\s*[0-9a-f]{64}$/m.test(split.data.rawFrontmatter)) return content;
3583
- const sha256 = createHash5("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
3584
- const rawFrontmatter = split.data.rawFrontmatter.replace(/^sha256:\s*[0-9a-f]{64}$/m, `sha256: ${sha256}`);
3585
- return `---
3586
- ${rawFrontmatter}
3587
- ---
3588
- ${split.data.body}`;
3589
- }
3590
3719
  function appendLintFixLastOp(vault, fixed) {
3591
3720
  if (fixed.length === 0) return;
3592
3721
  appendLastOp(vault, {
@@ -3625,10 +3754,10 @@ function summarizeLintOutput(output, examplesLimit = 3) {
3625
3754
  };
3626
3755
  }
3627
3756
  async function walkMarkdownFiles(absDir, vaultRoot) {
3628
- const entries = await readdir4(absDir, { withFileTypes: true });
3757
+ const entries = await readdir3(absDir, { withFileTypes: true });
3629
3758
  const pages = [];
3630
3759
  for (const entry of entries) {
3631
- const absPath = join18(absDir, entry.name);
3760
+ const absPath = join16(absDir, entry.name);
3632
3761
  if (entry.isDirectory()) {
3633
3762
  if (entry.name === ".git" || entry.name === "node_modules") continue;
3634
3763
  pages.push(...await walkMarkdownFiles(absPath, vaultRoot));
@@ -3639,12 +3768,12 @@ async function walkMarkdownFiles(absDir, vaultRoot) {
3639
3768
  return pages;
3640
3769
  }
3641
3770
  async function collectCliRefsPages(vault) {
3642
- if (!existsSync6(join18(vault, "SCHEMA.md"))) {
3771
+ if (!existsSync6(join16(vault, "SCHEMA.md"))) {
3643
3772
  return err("VAULT_PATH_INVALID", { root: vault, reason: "SCHEMA.md missing" });
3644
3773
  }
3645
3774
  const pages = [];
3646
3775
  for (const dir of CLI_REFS_TYPED_DIRS) {
3647
- const absDir = join18(vault, dir);
3776
+ const absDir = join16(vault, dir);
3648
3777
  if (!existsSync6(absDir)) continue;
3649
3778
  pages.push(...await walkMarkdownFiles(absDir, vault));
3650
3779
  }
@@ -3764,9 +3893,13 @@ async function applyFileSourceUrlFix(input, scan, fileSourceUrlFlags, fileSource
3764
3893
  if (!input.fix || fileSourceUrlFrontmatterFlags.size === 0) return fileSourceUrlFlags;
3765
3894
  const fileFixed = [];
3766
3895
  for (const relPath of fileSourceUrlFrontmatterFlags) {
3896
+ if (relPath.startsWith("raw/")) {
3897
+ unresolved.push(relPath);
3898
+ continue;
3899
+ }
3767
3900
  try {
3768
3901
  const absPath = `${input.vault}/${relPath}`;
3769
- const raw = await readFile14(absPath, "utf8");
3902
+ const raw = await readFile15(absPath, "utf8");
3770
3903
  const parts = raw.split("---", 3);
3771
3904
  if (parts.length < 3) {
3772
3905
  unresolved.push(relPath);
@@ -4159,8 +4292,8 @@ async function runLint(input) {
4159
4292
  const readKnowledgeContent = (slug) => {
4160
4293
  const existing = knowledgeContentCache.get(slug);
4161
4294
  if (existing) return existing;
4162
- const knowledgePath = join18(lintVault, "projects", slug, "knowledge.md");
4163
- const pending = existsSync6(knowledgePath) ? readFile14(knowledgePath, "utf8").catch(() => null) : Promise.resolve(null);
4295
+ const knowledgePath = join16(lintVault, "projects", slug, "knowledge.md");
4296
+ const pending = existsSync6(knowledgePath) ? readFile15(knowledgePath, "utf8").catch(() => null) : Promise.resolve(null);
4164
4297
  knowledgeContentCache.set(slug, pending);
4165
4298
  return pending;
4166
4299
  };
@@ -4237,8 +4370,11 @@ async function runLint(input) {
4237
4370
  const raw = await readPage(page);
4238
4371
  const redacted = redactSensitiveContent(raw, { file: page.relPath });
4239
4372
  if (!redacted.changed) continue;
4240
- const next = recomputeRawSha256IfPresent(redacted.text);
4241
- const w = await safeWritePage(page.absPath, next, { minBodyRatio: null });
4373
+ if (page.relPath.startsWith("raw/")) {
4374
+ unresolved.push(page.relPath);
4375
+ continue;
4376
+ }
4377
+ const w = await safeWritePage(page.absPath, redacted.text, { minBodyRatio: null });
4242
4378
  if (!w.ok) {
4243
4379
  unresolved.push(page.relPath);
4244
4380
  continue;
@@ -4266,7 +4402,7 @@ async function runLint(input) {
4266
4402
  for (const relPath of legacyPages) {
4267
4403
  try {
4268
4404
  const absPath = `${input.vault}/${relPath}`;
4269
- const raw = await readFile14(absPath, "utf8");
4405
+ const raw = await readFile15(absPath, "utf8");
4270
4406
  const split = splitFrontmatter(raw);
4271
4407
  if (!split.ok) {
4272
4408
  unresolved.push(relPath);
@@ -4365,7 +4501,7 @@ ${newBody}`;
4365
4501
  for (const relPath of noOverview) {
4366
4502
  try {
4367
4503
  const absPath = `${input.vault}/${relPath}`;
4368
- const raw = await readFile14(absPath, "utf8");
4504
+ const raw = await readFile15(absPath, "utf8");
4369
4505
  const split = splitFrontmatter(raw);
4370
4506
  if (!split.ok) {
4371
4507
  unresolved.push(relPath);
@@ -4406,7 +4542,7 @@ ${trimmedBody}`;
4406
4542
  for (const relPath of missingTldrFlags) {
4407
4543
  try {
4408
4544
  const absPath = `${input.vault}/${relPath}`;
4409
- const raw = await readFile14(absPath, "utf8");
4545
+ const raw = await readFile15(absPath, "utf8");
4410
4546
  const split = splitFrontmatter(raw);
4411
4547
  if (!split.ok) {
4412
4548
  unresolved.push(relPath);
@@ -4456,7 +4592,7 @@ ${lines.join("\n")}`;
4456
4592
  for (const relPath of wikilinkCitationFlags) {
4457
4593
  try {
4458
4594
  const absPath = `${input.vault}/${relPath}`;
4459
- const raw = await readFile14(absPath, "utf8");
4595
+ const raw = await readFile15(absPath, "utf8");
4460
4596
  const split = splitFrontmatter(raw);
4461
4597
  if (!split.ok) {
4462
4598
  unresolved.push(relPath);
@@ -4561,6 +4697,11 @@ ${newBody}`;
4561
4697
  const invalidItems = buckets.frontmatter_yaml_invalid;
4562
4698
  const remaining = [];
4563
4699
  for (const item of invalidItems) {
4700
+ if (item.path.startsWith("raw/")) {
4701
+ unresolved.push(item.path);
4702
+ remaining.push(item);
4703
+ continue;
4704
+ }
4564
4705
  const page = scan.allMarkdown.find((p) => p.relPath === item.path);
4565
4706
  if (!page) {
4566
4707
  unresolved.push(item.path);
@@ -4797,14 +4938,14 @@ async function runSyncLintDelta(input) {
4797
4938
  }
4798
4939
 
4799
4940
  // src/commands/config.ts
4800
- import { readFile as readFile15 } from "fs/promises";
4941
+ import { readFile as readFile16 } from "fs/promises";
4801
4942
  import { existsSync as existsSync7 } from "fs";
4802
- import { join as join19 } from "path";
4943
+ import { join as join17 } from "path";
4803
4944
  function validateKey(key) {
4804
4945
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key);
4805
4946
  }
4806
4947
  function configPath(home) {
4807
- return join19(home, ".skillwiki", ".env");
4948
+ return join17(home, ".skillwiki", ".env");
4808
4949
  }
4809
4950
  async function runConfigGet(input) {
4810
4951
  if (!validateKey(input.key)) {
@@ -4828,7 +4969,7 @@ async function runConfigSet(input) {
4828
4969
  try {
4829
4970
  let originalContent;
4830
4971
  try {
4831
- originalContent = await readFile15(filePath, "utf8");
4972
+ originalContent = await readFile16(filePath, "utf8");
4832
4973
  } catch {
4833
4974
  }
4834
4975
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -4865,19 +5006,19 @@ async function runConfigPath(input) {
4865
5006
 
4866
5007
  // src/commands/doctor.ts
4867
5008
  import { existsSync as existsSync13, lstatSync, readlinkSync, readdirSync as readdirSync3, statSync as statSync2, readFileSync as readFileSync10 } from "fs";
4868
- import { join as join25, resolve as resolve5 } from "path";
5009
+ import { join as join23, resolve as resolve5 } from "path";
4869
5010
  import { execSync as execSync2 } from "child_process";
4870
5011
  import { platform as platform2 } from "os";
4871
5012
 
4872
5013
  // src/utils/plugin-registry.ts
4873
5014
  import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync5 } from "fs";
4874
- import { join as join20 } from "path";
4875
- var REGISTRY_PATH = join20(".claude", "plugins", "installed_plugins.json");
4876
- var CODEX_CONFIG_PATH = join20(".codex", "config.toml");
5015
+ import { join as join18 } from "path";
5016
+ var REGISTRY_PATH = join18(".claude", "plugins", "installed_plugins.json");
5017
+ var CODEX_CONFIG_PATH = join18(".codex", "config.toml");
4877
5018
  var PLUGIN_KEY = "skillwiki@llm-wiki";
4878
5019
  function readInstalledPlugins(home) {
4879
5020
  try {
4880
- const raw = readFileSync5(join20(home, REGISTRY_PATH), "utf8");
5021
+ const raw = readFileSync5(join18(home, REGISTRY_PATH), "utf8");
4881
5022
  return JSON.parse(raw);
4882
5023
  } catch {
4883
5024
  return null;
@@ -4913,7 +5054,7 @@ function findPluginInstallations(home, key = PLUGIN_KEY) {
4913
5054
  function findCodexPlugin(home, key, pluginName, marketplace) {
4914
5055
  const config = readCodexPluginConfig(home, key, marketplace);
4915
5056
  if (!config?.enabled) return null;
4916
- const cacheRoot = join20(home, ".codex", "plugins", "cache", marketplace, pluginName);
5057
+ const cacheRoot = join18(home, ".codex", "plugins", "cache", marketplace, pluginName);
4917
5058
  if (!existsSync8(cacheRoot)) return null;
4918
5059
  let versions;
4919
5060
  try {
@@ -4929,7 +5070,7 @@ function findCodexPlugin(home, key, pluginName, marketplace) {
4929
5070
  key,
4930
5071
  pluginName,
4931
5072
  marketplace,
4932
- installPath: join20(cacheRoot, version),
5073
+ installPath: join18(cacheRoot, version),
4933
5074
  version,
4934
5075
  sourceType: config.sourceType,
4935
5076
  source: config.source
@@ -4946,7 +5087,7 @@ function parsePluginKey(key) {
4946
5087
  function readCodexPluginConfig(home, key, marketplace) {
4947
5088
  let raw;
4948
5089
  try {
4949
- raw = readFileSync5(join20(home, CODEX_CONFIG_PATH), "utf8");
5090
+ raw = readFileSync5(join18(home, CODEX_CONFIG_PATH), "utf8");
4950
5091
  } catch {
4951
5092
  return null;
4952
5093
  }
@@ -4989,7 +5130,7 @@ function parseTomlScalar(rawValue) {
4989
5130
 
4990
5131
  // src/utils/conflict-markers.ts
4991
5132
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
4992
- import { join as join21 } from "path";
5133
+ import { join as join19 } from "path";
4993
5134
  function scanConflictMarkerBlocksInText(relPath, text) {
4994
5135
  const findings = [];
4995
5136
  const lines = text.split(/\r?\n/);
@@ -5040,7 +5181,7 @@ function walkMarkdownFiles2(root, dir, rel, out) {
5040
5181
  for (const entry of entries) {
5041
5182
  if (entry.isDirectory()) {
5042
5183
  if (PRUNE_DIRS.has(entry.name)) continue;
5043
- walkMarkdownFiles2(root, join21(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name, out);
5184
+ walkMarkdownFiles2(root, join19(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name, out);
5044
5185
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
5045
5186
  out.push(rel ? `${rel}/${entry.name}` : entry.name);
5046
5187
  }
@@ -5054,7 +5195,7 @@ function scanVaultConflictMarkers(vaultRoot) {
5054
5195
  for (const rel of relPaths) {
5055
5196
  let text;
5056
5197
  try {
5057
- text = readFileSync6(join21(vaultRoot, rel), "utf8");
5198
+ text = readFileSync6(join19(vaultRoot, rel), "utf8");
5058
5199
  } catch {
5059
5200
  continue;
5060
5201
  }
@@ -5065,7 +5206,7 @@ function scanVaultConflictMarkers(vaultRoot) {
5065
5206
 
5066
5207
  // src/utils/remote-health.ts
5067
5208
  import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
5068
- import { join as join22 } from "path";
5209
+ import { join as join20 } from "path";
5069
5210
  import { execFileSync } from "child_process";
5070
5211
  var REMOTE_PROBE_TIMEOUT_MS = 3e3;
5071
5212
  var defaultExec = (file, args, cwd) => execFileSync(file, args, {
@@ -5076,7 +5217,7 @@ var defaultExec = (file, args, cwd) => execFileSync(file, args, {
5076
5217
  }).trim();
5077
5218
  function readWikiS3RemoteConfigured(home) {
5078
5219
  try {
5079
- const content = readFileSync7(join22(home, ".skillwiki", ".env"), "utf8");
5220
+ const content = readFileSync7(join20(home, ".skillwiki", ".env"), "utf8");
5080
5221
  for (const line of content.split(/\r?\n/)) {
5081
5222
  const trimmed = line.trim();
5082
5223
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -5101,7 +5242,7 @@ function resolveWikiS3Remote(input) {
5101
5242
  return readWikiS3RemoteConfigured(input.home);
5102
5243
  }
5103
5244
  function probeGithubReachability(vaultPath, exec = defaultExec) {
5104
- if (!existsSync10(join22(vaultPath, ".git"))) return "unknown";
5245
+ if (!existsSync10(join20(vaultPath, ".git"))) return "unknown";
5105
5246
  try {
5106
5247
  exec("git", ["remote", "get-url", "origin"], vaultPath);
5107
5248
  } catch {
@@ -5167,10 +5308,10 @@ function probeRemoteHealth(input) {
5167
5308
 
5168
5309
  // src/utils/satellite-run-health.ts
5169
5310
  import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
5170
- import { join as join23 } from "path";
5311
+ import { join as join21 } from "path";
5171
5312
  var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
5172
5313
  function satelliteLatestRunPath(vault) {
5173
- return join23(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5314
+ return join21(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5174
5315
  }
5175
5316
  function isFailedRunStatus(status) {
5176
5317
  return status === "fail" || status === "failure";
@@ -5223,8 +5364,8 @@ function evaluateSatelliteRunHealth(vault, now) {
5223
5364
  // src/utils/s3-mount-health.ts
5224
5365
  import { execSync } from "child_process";
5225
5366
  import { platform } from "os";
5226
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5, readFileSync as readFile16 } from "fs";
5227
- import { join as join24 } from "path";
5367
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile17 } from "fs";
5368
+ import { join as join22 } from "path";
5228
5369
  var OS = platform();
5229
5370
  function findRcloneMountPid() {
5230
5371
  try {
@@ -5382,7 +5523,7 @@ function detectFuseMount(vaultPath) {
5382
5523
  return null;
5383
5524
  }
5384
5525
  function writeTest(dir) {
5385
- const testFile = join24(dir, `.doctor-write-test-${process.pid}.tmp`);
5526
+ const testFile = join22(dir, `.doctor-write-test-${process.pid}.tmp`);
5386
5527
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
5387
5528
  const start = Date.now();
5388
5529
  try {
@@ -5393,24 +5534,24 @@ function writeTest(dir) {
5393
5534
  const writeMs = Date.now() - start;
5394
5535
  const readStart = Date.now();
5395
5536
  try {
5396
- const back = readFile16(testFile, "utf8");
5537
+ const back = readFile17(testFile, "utf8");
5397
5538
  const readMs = Date.now() - readStart;
5398
5539
  if (back !== payload) {
5399
5540
  try {
5400
- unlinkSync5(testFile);
5541
+ unlinkSync4(testFile);
5401
5542
  } catch {
5402
5543
  }
5403
5544
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
5404
5545
  }
5405
5546
  } catch (e) {
5406
5547
  try {
5407
- unlinkSync5(testFile);
5548
+ unlinkSync4(testFile);
5408
5549
  } catch {
5409
5550
  }
5410
5551
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
5411
5552
  }
5412
5553
  try {
5413
- unlinkSync5(testFile);
5554
+ unlinkSync4(testFile);
5414
5555
  } catch {
5415
5556
  }
5416
5557
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -5482,12 +5623,12 @@ function detectCliChannels(argv, home) {
5482
5623
  }
5483
5624
  const plugin = findPlugin(home);
5484
5625
  if (plugin) {
5485
- const pluginBin = join25(plugin.installPath, "bin", "skillwiki");
5626
+ const pluginBin = join23(plugin.installPath, "bin", "skillwiki");
5486
5627
  if (existsSync13(pluginBin)) {
5487
5628
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
5488
5629
  }
5489
5630
  }
5490
- const installBin = join25(home, ".claude", "skills", "bin", "skillwiki");
5631
+ const installBin = join23(home, ".claude", "skills", "bin", "skillwiki");
5491
5632
  if (existsSync13(installBin)) {
5492
5633
  channels.push({ name: "install", path: installBin, isDevLink: false });
5493
5634
  }
@@ -5577,9 +5718,9 @@ function checkVaultStructure(resolvedPath) {
5577
5718
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
5578
5719
  }
5579
5720
  const missing = [];
5580
- if (!existsSync13(join25(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5721
+ if (!existsSync13(join23(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5581
5722
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
5582
- if (!existsSync13(join25(resolvedPath, dir))) missing.push(dir + "/");
5723
+ if (!existsSync13(join23(resolvedPath, dir))) missing.push(dir + "/");
5583
5724
  }
5584
5725
  if (missing.length === 0) {
5585
5726
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -5587,7 +5728,7 @@ function checkVaultStructure(resolvedPath) {
5587
5728
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
5588
5729
  }
5589
5730
  function checkSkillsInstalled(home, cwd) {
5590
- const srcDir = cwd ? join25(cwd, "packages", "skills") : void 0;
5731
+ const srcDir = cwd ? join23(cwd, "packages", "skills") : void 0;
5591
5732
  if (srcDir && existsSync13(srcDir)) {
5592
5733
  const found = findInstalledSkillMd(srcDir);
5593
5734
  if (found.length > 0) {
@@ -5601,7 +5742,7 @@ function checkSkillsInstalled(home, cwd) {
5601
5742
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
5602
5743
  }
5603
5744
  }
5604
- const skillsDir = join25(home, ".claude", "skills");
5745
+ const skillsDir = join23(home, ".claude", "skills");
5605
5746
  if (existsSync13(skillsDir)) {
5606
5747
  const found = findInstalledSkillMd(skillsDir);
5607
5748
  if (found.length > 0) {
@@ -5612,10 +5753,10 @@ function checkSkillsInstalled(home, cwd) {
5612
5753
  }
5613
5754
  function checkDuplicateSkills(home) {
5614
5755
  const plugin = findPlugin(home);
5615
- const skillsDir = join25(home, ".claude", "skills");
5756
+ const skillsDir = join23(home, ".claude", "skills");
5616
5757
  const agentSkillDirs = [
5617
- { label: "~/.codex/skills/", path: join25(home, ".codex", "skills") },
5618
- { label: "~/.agents/skills/", path: join25(home, ".agents", "skills") }
5758
+ { label: "~/.codex/skills/", path: join23(home, ".codex", "skills") },
5759
+ { label: "~/.agents/skills/", path: join23(home, ".agents", "skills") }
5619
5760
  ];
5620
5761
  if (!plugin) {
5621
5762
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -5718,7 +5859,7 @@ async function checkProfiles(home) {
5718
5859
  }
5719
5860
  async function checkProjectLocalOverride(cwd) {
5720
5861
  const dir = cwd ?? process.cwd();
5721
- const envPath = join25(dir, ".skillwiki", ".env");
5862
+ const envPath = join23(dir, ".skillwiki", ".env");
5722
5863
  if (existsSync13(envPath)) {
5723
5864
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
5724
5865
  }
@@ -5728,7 +5869,7 @@ function checkVaultGitRemote(resolvedPath) {
5728
5869
  if (resolvedPath === void 0) {
5729
5870
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
5730
5871
  }
5731
- if (!existsSync13(join25(resolvedPath, ".git"))) {
5872
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5732
5873
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
5733
5874
  }
5734
5875
  try {
@@ -5751,9 +5892,9 @@ function checkObsidianTemplates(resolvedPath) {
5751
5892
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
5752
5893
  }
5753
5894
  const missing = [];
5754
- if (!existsSync13(join25(resolvedPath, "_Templates"))) missing.push("_Templates/");
5755
- if (!existsSync13(join25(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5756
- if (!existsSync13(join25(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
5895
+ if (!existsSync13(join23(resolvedPath, "_Templates"))) missing.push("_Templates/");
5896
+ if (!existsSync13(join23(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5897
+ if (!existsSync13(join23(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
5757
5898
  if (missing.length === 0) {
5758
5899
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
5759
5900
  }
@@ -5763,7 +5904,7 @@ function checkDotStoreClean(resolvedPath) {
5763
5904
  if (resolvedPath === void 0) {
5764
5905
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
5765
5906
  }
5766
- const rawDir = join25(resolvedPath, "raw");
5907
+ const rawDir = join23(resolvedPath, "raw");
5767
5908
  if (!existsSync13(rawDir)) {
5768
5909
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
5769
5910
  }
@@ -5779,7 +5920,7 @@ function checkDotStoreClean(resolvedPath) {
5779
5920
  if (entry.name === ".DS_Store") {
5780
5921
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
5781
5922
  } else if (entry.isDirectory()) {
5782
- walk(join25(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5923
+ walk(join23(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5783
5924
  }
5784
5925
  }
5785
5926
  })(rawDir, "");
@@ -5810,7 +5951,7 @@ function checkSyncLastPush(resolvedPath) {
5810
5951
  if (resolvedPath === void 0) {
5811
5952
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
5812
5953
  }
5813
- if (!existsSync13(join25(resolvedPath, ".git"))) {
5954
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5814
5955
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
5815
5956
  }
5816
5957
  let timestamp;
@@ -5859,7 +6000,7 @@ function checkVaultGitDirty(resolvedPath) {
5859
6000
  if (resolvedPath === void 0) {
5860
6001
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
5861
6002
  }
5862
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6003
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5863
6004
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
5864
6005
  }
5865
6006
  try {
@@ -5928,7 +6069,7 @@ function remoteMainHash(resolvedPath) {
5928
6069
  }
5929
6070
  function checkStaleRemoteMain(resolvedPath) {
5930
6071
  if (resolvedPath === void 0) return void 0;
5931
- if (!existsSync13(join25(resolvedPath, ".git"))) return void 0;
6072
+ if (!existsSync13(join23(resolvedPath, ".git"))) return void 0;
5932
6073
  const localOrigin = gitRefHash(resolvedPath, "origin/main");
5933
6074
  if (!localOrigin) return void 0;
5934
6075
  const remoteMain = remoteMainHash(resolvedPath);
@@ -5944,7 +6085,7 @@ function checkVaultLocalGit(resolvedPath) {
5944
6085
  if (resolvedPath === void 0) {
5945
6086
  return check("warn", "vault_local_git", "Vault local git", "Cannot check \u2014 WIKI_PATH not resolved");
5946
6087
  }
5947
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6088
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5948
6089
  return check("warn", "vault_local_git", "Vault local git", "Not a git repository - sync features unavailable");
5949
6090
  }
5950
6091
  try {
@@ -5963,7 +6104,7 @@ function checkVaultGithubRemote(resolvedPath, exec) {
5963
6104
  if (resolvedPath === void 0) {
5964
6105
  return check("pass", "vault_github_remote", "Vault GitHub remote", "No vault path \u2014 check skipped");
5965
6106
  }
5966
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6107
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5967
6108
  return check("pass", "vault_github_remote", "Vault GitHub remote", "No git repo \u2014 check skipped");
5968
6109
  }
5969
6110
  const state = probeGithubReachability(resolvedPath, exec);
@@ -6007,7 +6148,7 @@ function checkVaultPromotionLag(resolvedPath) {
6007
6148
  if (resolvedPath === void 0) {
6008
6149
  return check("pass", "vault_promotion_lag", "Vault promotion lag", "No vault path \u2014 check skipped");
6009
6150
  }
6010
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6151
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
6011
6152
  return check("pass", "vault_promotion_lag", "Vault promotion lag", "No git repo \u2014 check skipped");
6012
6153
  }
6013
6154
  try {
@@ -6034,7 +6175,7 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
6034
6175
  if (resolvedPath === void 0) {
6035
6176
  return check("pass", id, label, "No vault path \u2014 check skipped");
6036
6177
  }
6037
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6178
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
6038
6179
  return check("pass", id, label, "No git repo \u2014 check skipped");
6039
6180
  }
6040
6181
  if (!hasOriginMain(resolvedPath)) {
@@ -6151,11 +6292,11 @@ async function checkFleetIdentity(input) {
6151
6292
  }
6152
6293
  function pullLogPaths(home) {
6153
6294
  const paths = platform2() === "darwin" ? [
6154
- join25(home, "Library", "Logs", "wiki-pull.log"),
6155
- join25(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
6295
+ join23(home, "Library", "Logs", "wiki-pull.log"),
6296
+ join23(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
6156
6297
  ] : [
6157
- join25(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
6158
- join25(home, "Library", "Logs", "wiki-pull.log")
6298
+ join23(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
6299
+ join23(home, "Library", "Logs", "wiki-pull.log")
6159
6300
  ];
6160
6301
  return [...new Set(paths)];
6161
6302
  }
@@ -6195,7 +6336,7 @@ function checkS3MountPerf(resolvedPath) {
6195
6336
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
6196
6337
  }
6197
6338
  const mountPoint = fuse.mountPoint;
6198
- const conceptsDir = join25(resolvedPath, "concepts");
6339
+ const conceptsDir = join23(resolvedPath, "concepts");
6199
6340
  if (!existsSync13(conceptsDir)) {
6200
6341
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
6201
6342
  }
@@ -6378,7 +6519,7 @@ function checkWriteTest(resolvedPath) {
6378
6519
  if (!fuse) {
6379
6520
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
6380
6521
  }
6381
- const conceptsDir = join25(resolvedPath, "concepts");
6522
+ const conceptsDir = join23(resolvedPath, "concepts");
6382
6523
  if (!existsSync13(conceptsDir)) {
6383
6524
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
6384
6525
  }
@@ -6480,7 +6621,7 @@ function checkVaultSyncPullHelper(home, env) {
6480
6621
  );
6481
6622
  }
6482
6623
  function checkVaultSyncReviewRequiredJournals(vaultPath) {
6483
- if (!vaultPath || !existsSync13(join25(vaultPath, ".git"))) {
6624
+ if (!vaultPath || !existsSync13(join23(vaultPath, ".git"))) {
6484
6625
  return check("pass", "vault_sync_review_required_journals", "Review-required journals", "No git vault \u2014 check skipped");
6485
6626
  }
6486
6627
  try {
@@ -6501,7 +6642,7 @@ function checkVaultSyncReviewRequiredJournals(vaultPath) {
6501
6642
  }
6502
6643
  function readVaultSyncConfig(home) {
6503
6644
  try {
6504
- const content = readFileSync10(join25(home, ".skillwiki", ".env"), "utf8");
6645
+ const content = readFileSync10(join23(home, ".skillwiki", ".env"), "utf8");
6505
6646
  let installed = false;
6506
6647
  let role;
6507
6648
  let serviceScope;
@@ -6630,7 +6771,7 @@ function snapshotterHealthChecks(scope, logDir, env) {
6630
6771
  let completionOutcome = "unknown";
6631
6772
  const logRecords = fixture ? fixture.log_records : (() => {
6632
6773
  try {
6633
- const content = readFileSync10(join25(logDir, "wiki-snapshot.log"), "utf8");
6774
+ const content = readFileSync10(join23(logDir, "wiki-snapshot.log"), "utf8");
6634
6775
  return content.split(/\r?\n/).filter(Boolean);
6635
6776
  } catch {
6636
6777
  return [];
@@ -6700,10 +6841,10 @@ function vaultSyncChecks(input) {
6700
6841
  ];
6701
6842
  }
6702
6843
  const isMac = os === "darwin";
6703
- const logDir = input.logDir ?? (isMac ? join25(home, "Library", "Logs") : join25(home, ".local", "state", "vault-sync", "log"));
6704
- const shareDir = input.shareDir ?? (isMac ? join25(home, "Library", "Application Support", "vault-sync", "bin") : join25(home, ".local", "share", "vault-sync", "bin"));
6705
- const filterPath = input.filterPath ?? join25(home, ".config", "rclone", "wiki-push-filters.txt");
6706
- const packagedSnapshotPath = join25(shareDir, "wiki-snapshot.sh");
6844
+ const logDir = input.logDir ?? (isMac ? join23(home, "Library", "Logs") : join23(home, ".local", "state", "vault-sync", "log"));
6845
+ const shareDir = input.shareDir ?? (isMac ? join23(home, "Library", "Application Support", "vault-sync", "bin") : join23(home, ".local", "share", "vault-sync", "bin"));
6846
+ const filterPath = input.filterPath ?? join23(home, ".config", "rclone", "wiki-push-filters.txt");
6847
+ const packagedSnapshotPath = join23(shareDir, "wiki-snapshot.sh");
6707
6848
  const legacySnapshotPath = "/root/.hermes/scripts/wiki-snapshot-v3.sh";
6708
6849
  const snapshotPath = input.snapshotScriptPath ?? (existsSync13(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
6709
6850
  if (input.vaultSyncRole === "snapshotter") {
@@ -6759,7 +6900,7 @@ function vaultSyncChecks(input) {
6759
6900
  }
6760
6901
  return [c12, ...healthChecks, cFetch2, c42, c52];
6761
6902
  }
6762
- const pushScriptPath = join25(shareDir, "wiki-push.sh");
6903
+ const pushScriptPath = join23(shareDir, "wiki-push.sh");
6763
6904
  const c1 = existsSync13(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
6764
6905
  let c2;
6765
6906
  try {
@@ -6811,7 +6952,7 @@ function vaultSyncChecks(input) {
6811
6952
  "Scheduler check failed \u2014 run vault-sync-install"
6812
6953
  );
6813
6954
  }
6814
- const logFile = join25(logDir, "wiki-push.log");
6955
+ const logFile = join23(logDir, "wiki-push.log");
6815
6956
  let c3;
6816
6957
  try {
6817
6958
  const logContent = readFileSync10(logFile, "utf8");
@@ -6882,7 +7023,7 @@ function vaultSyncChecks(input) {
6882
7023
  `Log directory not found at ${logDir}`
6883
7024
  );
6884
7025
  }
6885
- const fetchLogFile = join25(logDir, "wiki-fetch.log");
7026
+ const fetchLogFile = join23(logDir, "wiki-fetch.log");
6886
7027
  let cFetch;
6887
7028
  try {
6888
7029
  const logContent = readFileSync10(fetchLogFile, "utf8");
@@ -7026,15 +7167,15 @@ function findSkillMd(dir) {
7026
7167
  }
7027
7168
  for (const entry of entries) {
7028
7169
  if (entry.isFile() && entry.name === "SKILL.md") {
7029
- results.push(join25(dir, entry.name));
7170
+ results.push(join23(dir, entry.name));
7030
7171
  } else if (entry.isDirectory()) {
7031
- results.push(...findSkillMd(join25(dir, entry.name)));
7172
+ results.push(...findSkillMd(join23(dir, entry.name)));
7032
7173
  }
7033
7174
  }
7034
7175
  return results;
7035
7176
  }
7036
7177
  function findInstalledSkillMd(dir) {
7037
- const directSkills = findSkillNames(dir).map((name) => join25(dir, name, "SKILL.md"));
7178
+ const directSkills = findSkillNames(dir).map((name) => join23(dir, name, "SKILL.md"));
7038
7179
  return directSkills.length > 0 ? directSkills : findSkillMd(dir);
7039
7180
  }
7040
7181
  function findSkillNames(dir) {
@@ -7046,7 +7187,7 @@ function findSkillNames(dir) {
7046
7187
  return results;
7047
7188
  }
7048
7189
  for (const entry of entries) {
7049
- if (entry.isDirectory() && existsSync13(join25(dir, entry.name, "SKILL.md"))) {
7190
+ if (entry.isDirectory() && existsSync13(join23(dir, entry.name, "SKILL.md"))) {
7050
7191
  results.push(entry.name);
7051
7192
  }
7052
7193
  }
@@ -7094,7 +7235,7 @@ async function vaultMetrics(resolvedPath) {
7094
7235
  }
7095
7236
  let logLines = 0;
7096
7237
  try {
7097
- logLines = readFileSync10(join25(scanRoot, "log.md"), "utf8").split("\n").length;
7238
+ logLines = readFileSync10(join23(scanRoot, "log.md"), "utf8").split("\n").length;
7098
7239
  } catch {
7099
7240
  }
7100
7241
  return [
@@ -7229,7 +7370,7 @@ function readCliPackageJson(baseUrl = import.meta.url) {
7229
7370
  // src/utils/vault-write-gates.ts
7230
7371
  import { execFileSync as execFileSync2 } from "child_process";
7231
7372
  import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
7232
- import { join as join26, relative as relative3 } from "path";
7373
+ import { join as join24, relative as relative3 } from "path";
7233
7374
  var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
7234
7375
  var DEFAULT_CAPTURE_BUDGET = 20;
7235
7376
  var DEFAULT_NO_DECISION_STREAK = 3;
@@ -7328,7 +7469,7 @@ function measureDirtyVolume(vault) {
7328
7469
  if (!rel) continue;
7329
7470
  if (code === "??") {
7330
7471
  untracked += 1;
7331
- const abs = join26(vault, rel);
7472
+ const abs = join24(vault, rel);
7332
7473
  if (existsSync14(abs) && statSync3(abs).isDirectory()) {
7333
7474
  const files = listFilesRecursive(abs);
7334
7475
  expanded += files.length;
@@ -7365,7 +7506,7 @@ function listFilesRecursive(dir) {
7365
7506
  }
7366
7507
  for (const name of entries) {
7367
7508
  if (name === ".git") continue;
7368
- const p = join26(dir, name);
7509
+ const p = join24(dir, name);
7369
7510
  try {
7370
7511
  const st = statSync3(p);
7371
7512
  if (st.isDirectory()) out.push(...listFilesRecursive(p));
@@ -7496,7 +7637,7 @@ function listProjectDayCaptures(vault, project, day) {
7496
7637
  return;
7497
7638
  }
7498
7639
  for (const name of entries) {
7499
- const abs = join26(dir, name);
7640
+ const abs = join24(dir, name);
7500
7641
  const rel = relBase ? `${relBase}/${name}` : name;
7501
7642
  try {
7502
7643
  const st = statSync3(abs);
@@ -7506,16 +7647,16 @@ function listProjectDayCaptures(vault, project, day) {
7506
7647
  }
7507
7648
  }
7508
7649
  };
7509
- walk(join26(vault, "raw", "transcripts"), "raw/transcripts");
7510
- walk(join26(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
7511
- walk(join26(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
7512
- const workRoot = join26(vault, "projects", slug, "work");
7650
+ walk(join24(vault, "raw", "transcripts"), "raw/transcripts");
7651
+ walk(join24(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
7652
+ walk(join24(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
7653
+ const workRoot = join24(vault, "projects", slug, "work");
7513
7654
  if (existsSync14(workRoot)) {
7514
7655
  try {
7515
7656
  for (const name of readdirSync4(workRoot)) {
7516
7657
  if (!name.startsWith(day)) continue;
7517
7658
  if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
7518
- const abs = join26(workRoot, name);
7659
+ const abs = join24(workRoot, name);
7519
7660
  if (statSync3(abs).isDirectory()) {
7520
7661
  for (const f of listFilesRecursive(abs)) {
7521
7662
  const rel = relative3(vault, f).replace(/\\/g, "/");
@@ -7621,8 +7762,8 @@ function runWritePreflight(input) {
7621
7762
  // src/commands/observe.ts
7622
7763
  import { mkdir as mkdir6, writeFile as writeFile4 } from "fs/promises";
7623
7764
  import { existsSync as existsSync15, statSync as statSync4 } from "fs";
7624
- import { join as join27 } from "path";
7625
- import { createHash as createHash6 } from "crypto";
7765
+ import { join as join25 } from "path";
7766
+ import { createHash as createHash7 } from "crypto";
7626
7767
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
7627
7768
  function slugify(text) {
7628
7769
  const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -7670,7 +7811,7 @@ async function runObserve(input) {
7670
7811
  };
7671
7812
  }
7672
7813
  }
7673
- const transcriptsDir = join27(input.vault, "raw", "transcripts");
7814
+ const transcriptsDir = join25(input.vault, "raw", "transcripts");
7674
7815
  try {
7675
7816
  await mkdir6(transcriptsDir, { recursive: true });
7676
7817
  } catch {
@@ -7682,16 +7823,16 @@ async function runObserve(input) {
7682
7823
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7683
7824
  const slug = slugify(input.text);
7684
7825
  const fileName = `${today}-observation-${slug}.md`;
7685
- const filePath = join27(transcriptsDir, fileName);
7826
+ const filePath = join25(transcriptsDir, fileName);
7686
7827
  const body = `
7687
7828
  ${input.text.trim()}
7688
7829
  `;
7689
- const sha256 = createHash6("sha256").update(Buffer.from(body, "utf8")).digest("hex");
7830
+ const sha2562 = createHash7("sha256").update(Buffer.from(body, "utf8")).digest("hex");
7690
7831
  const frontmatterLines = [
7691
7832
  "---",
7692
7833
  "source_url:",
7693
7834
  `ingested: ${today}`,
7694
- `sha256: ${sha256}`,
7835
+ `sha256: ${sha2562}`,
7695
7836
  `kind: ${kind}`
7696
7837
  ];
7697
7838
  if (input.project) {
@@ -7714,17 +7855,17 @@ ${input.text.trim()}
7714
7855
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7715
7856
  });
7716
7857
  const relPath = `raw/transcripts/${fileName}`;
7717
- const humanHint = `created ${relPath} (${sha256.slice(0, 12)}...)`;
7858
+ const humanHint = `created ${relPath} (${sha2562.slice(0, 12)}...)`;
7718
7859
  return {
7719
7860
  exitCode: ExitCode.OK,
7720
- result: ok({ path: relPath, sha256, humanHint })
7861
+ result: ok({ path: relPath, sha256: sha2562, humanHint })
7721
7862
  };
7722
7863
  }
7723
7864
 
7724
7865
  // src/commands/memory.ts
7725
- import { createHash as createHash7 } from "crypto";
7726
- import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile5 } from "fs/promises";
7727
- import { basename as basename2, extname, join as join28, relative as relative4, sep as sep3 } from "path";
7866
+ import { createHash as createHash8 } from "crypto";
7867
+ import { mkdir as mkdir7, readFile as readFile18, readdir as readdir4, stat as stat4, writeFile as writeFile5 } from "fs/promises";
7868
+ import { basename as basename2, extname, join as join26, relative as relative4, sep as sep3 } from "path";
7728
7869
 
7729
7870
  // src/utils/memory-authority.ts
7730
7871
  var TIER_RANK = {
@@ -7843,8 +7984,8 @@ async function runMemoryIndex(input) {
7843
7984
  }
7844
7985
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
7845
7986
  const relCachePath = memoryCacheRelPath(input.project);
7846
- const absCachePath = join28(input.vault, relCachePath);
7847
- await mkdir7(join28(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7987
+ const absCachePath = join26(input.vault, relCachePath);
7988
+ await mkdir7(join26(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7848
7989
  await writeFile5(absCachePath, `${JSON.stringify({
7849
7990
  generated_at: generatedAt,
7850
7991
  project: input.project,
@@ -8036,7 +8177,7 @@ async function buildMemoryIndexState(pages, project) {
8036
8177
  }
8037
8178
  async function checkMemoryIndex(vault, project, current) {
8038
8179
  const relCachePath = memoryCacheRelPath(project);
8039
- const cacheText = await readIfExists2(join28(vault, relCachePath));
8180
+ const cacheText = await readIfExists2(join26(vault, relCachePath));
8040
8181
  if (!cacheText) {
8041
8182
  return {
8042
8183
  ok: true,
@@ -8153,7 +8294,7 @@ async function readMemoryPage(page, project, warnings) {
8153
8294
  title,
8154
8295
  summary: summarize(body),
8155
8296
  updated,
8156
- hash: createHash7("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
8297
+ hash: createHash8("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
8157
8298
  topics,
8158
8299
  project,
8159
8300
  ...stringField(fm.data.memory_kind) ? { memory_kind: stringField(fm.data.memory_kind) } : {},
@@ -8355,7 +8496,7 @@ function matchPastFailureTarget(target, candidateBody) {
8355
8496
  }
8356
8497
  if (!FAILURE_MARKER_RE.test(candidateBody)) return null;
8357
8498
  const candidateTokens = new Set(tokenizeForMatch(candidate));
8358
- const overlappingTokens = target.distinctiveTokens.filter((token) => candidateTokens.has(token));
8499
+ const overlappingTokens = target.distinctiveTokens.filter((token2) => candidateTokens.has(token2));
8359
8500
  if (overlappingTokens.length >= 2) {
8360
8501
  return { reason: `distinctive target terms overlapped prior failure memory (${overlappingTokens.slice(0, 4).join(", ")})` };
8361
8502
  }
@@ -8371,7 +8512,7 @@ function extractCodeLikeNeedles(value) {
8371
8512
  return (value.match(/[A-Za-z0-9_.:-]*[_.:-][A-Za-z0-9_.:-]+/g) ?? []).filter((needle) => /[A-Za-z]/.test(needle));
8372
8513
  }
8373
8514
  function extractDistinctiveTargetTokens(value) {
8374
- return uniqueStrings(tokenizeForMatch(value).filter((token) => token.length >= 4).filter((token) => !/^\d+$/.test(token)).filter((token) => !COMMON_FAILURE_MATCH_TOKENS.has(token)));
8515
+ return uniqueStrings(tokenizeForMatch(value).filter((token2) => token2.length >= 4).filter((token2) => !/^\d+$/.test(token2)).filter((token2) => !COMMON_FAILURE_MATCH_TOKENS.has(token2)));
8375
8516
  }
8376
8517
  function hasDistinctiveNeedle(value) {
8377
8518
  return extractDistinctiveTargetTokens(value).length > 0;
@@ -8429,23 +8570,23 @@ function renderMemoryIndexStatusHint(status) {
8429
8570
  }
8430
8571
  async function readIfExists2(path) {
8431
8572
  try {
8432
- return await readFile17(path, "utf8");
8573
+ return await readFile18(path, "utf8");
8433
8574
  } catch {
8434
8575
  return "";
8435
8576
  }
8436
8577
  }
8437
8578
  async function collectImportFiles(source) {
8438
- const st = await stat5(source);
8579
+ const st = await stat4(source);
8439
8580
  if (st.isFile()) return isImportCandidate(source) ? [source] : [];
8440
8581
  const files = [];
8441
8582
  await walkImportFiles(source, files);
8442
8583
  return files.sort((a, b) => a.localeCompare(b));
8443
8584
  }
8444
8585
  async function walkImportFiles(dir, out) {
8445
- const entries = await readdir5(dir, { withFileTypes: true });
8586
+ const entries = await readdir4(dir, { withFileTypes: true });
8446
8587
  for (const entry of entries) {
8447
8588
  if (entry.name === ".git" || entry.name === "node_modules") continue;
8448
- const path = join28(dir, entry.name);
8589
+ const path = join26(dir, entry.name);
8449
8590
  if (entry.isDirectory()) {
8450
8591
  await walkImportFiles(path, out);
8451
8592
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -8458,9 +8599,9 @@ function isImportCandidate(path) {
8458
8599
  return ext === ".md" || ext === ".txt";
8459
8600
  }
8460
8601
  async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8461
- const st = await stat5(file);
8602
+ const st = await stat4(file);
8462
8603
  const sourceKind = classifyImportSource(file);
8463
- const hash = createHash7("sha256").update(await readFile17(file)).digest("hex");
8604
+ const hash = createHash8("sha256").update(await readFile18(file)).digest("hex");
8464
8605
  const baseEntry = {
8465
8606
  source_path: file,
8466
8607
  source_kind: sourceKind,
@@ -8483,7 +8624,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8483
8624
  reason: "policy_source_not_imported"
8484
8625
  };
8485
8626
  }
8486
- const text = await readFile17(file, "utf8");
8627
+ const text = await readFile18(file, "utf8");
8487
8628
  const extracted = extractImportText(text, sourceKind);
8488
8629
  if (!extracted) {
8489
8630
  return {
@@ -8512,8 +8653,8 @@ async function writeImportCapture(vault, entry, today) {
8512
8653
  const content = hiddenString(entry, "__content");
8513
8654
  const project = hiddenString(entry, "__project");
8514
8655
  const relPath = await availableImportPath(vault, entry.proposed_path);
8515
- const absPath = join28(vault, relPath);
8516
- await mkdir7(join28(vault, "raw", "transcripts"), { recursive: true });
8656
+ const absPath = join26(vault, relPath);
8657
+ await mkdir7(join26(vault, "raw", "transcripts"), { recursive: true });
8517
8658
  await writeFile5(absPath, renderImportCapture(entry, content, project, today), "utf8");
8518
8659
  const validation = await runValidate({ file: absPath });
8519
8660
  return {
@@ -8529,7 +8670,7 @@ async function availableImportPath(vault, proposed) {
8529
8670
  const stem = proposed.slice(0, -ext.length);
8530
8671
  let candidate = proposed;
8531
8672
  let i = 2;
8532
- while (await readIfExists2(join28(vault, candidate))) {
8673
+ while (await readIfExists2(join26(vault, candidate))) {
8533
8674
  candidate = `${stem}-${i}${ext}`;
8534
8675
  i++;
8535
8676
  }
@@ -8714,10 +8855,10 @@ function memoryCacheRelPath(project) {
8714
8855
  }
8715
8856
  async function readMemoryCache(vault, project) {
8716
8857
  if (project) {
8717
- const projectCache = await readIfExists2(join28(vault, memoryCacheRelPath(project)));
8858
+ const projectCache = await readIfExists2(join26(vault, memoryCacheRelPath(project)));
8718
8859
  if (projectCache) return projectCache;
8719
8860
  }
8720
- return readIfExists2(join28(vault, ".skillwiki", "memory-topics.json"));
8861
+ return readIfExists2(join26(vault, ".skillwiki", "memory-topics.json"));
8721
8862
  }
8722
8863
  function dedupePages(pages) {
8723
8864
  const seen = /* @__PURE__ */ new Set();
@@ -8808,8 +8949,8 @@ function slugify2(value) {
8808
8949
  }
8809
8950
 
8810
8951
  // src/commands/query.ts
8811
- import { readFile as readFile18, stat as stat6 } from "fs/promises";
8812
- import { join as join29 } from "path";
8952
+ import { readFile as readFile19, stat as stat5 } from "fs/promises";
8953
+ import { join as join27 } from "path";
8813
8954
  var W_KEYWORD = 2;
8814
8955
  var W_SOURCE_OVERLAP = 4;
8815
8956
  var W_WIKILINK = 3;
@@ -8875,8 +9016,22 @@ async function runQuery(input) {
8875
9016
  type: page.type
8876
9017
  };
8877
9018
  }).filter((r) => r.score > 0).sort((a, b) => b.score - a.score || a.path.localeCompare(b.path)).slice(0, limit);
8878
- const humanHint = results.length === 0 ? "no matching pages found" : results.map((r) => `${r.path} (score: ${r.score})`).join("\n");
8879
- return { exitCode: ExitCode.OK, result: ok({ results, humanHint }) };
9019
+ let pendingSources;
9020
+ if (input.includePending) {
9021
+ const { runSourcesPending } = await import("./sources-L2SQV63E.js");
9022
+ const pending = await runSourcesPending({
9023
+ vault: input.vault,
9024
+ match: input.text,
9025
+ limit: input.limit ?? 10
9026
+ });
9027
+ pendingSources = pending.result.ok ? pending.result.data.items : [];
9028
+ }
9029
+ const humanHint = results.length === 0 ? pendingSources && pendingSources.length > 0 ? `no matching typed pages found
9030
+ ${pendingSources.length} matching pending source(s)` : "no matching pages found" : results.map((r) => `${r.path} (score: ${r.score})`).join("\n");
9031
+ return {
9032
+ exitCode: ExitCode.OK,
9033
+ result: ok({ results, ...pendingSources ? { pending_sources: pendingSources } : {}, humanHint })
9034
+ };
8880
9035
  }
8881
9036
  function scoreSourceOverlap(page, allPages, seedPaths) {
8882
9037
  if (page.sources.length === 0) return 0;
@@ -8930,10 +9085,10 @@ function computeKeywordScore(terms, title, tags, body) {
8930
9085
  return score;
8931
9086
  }
8932
9087
  async function loadOrBuildGraph(vault) {
8933
- const graphPath = join29(vault, ".skillwiki", "graph.json");
9088
+ const graphPath = join27(vault, ".skillwiki", "graph.json");
8934
9089
  let needsBuild = false;
8935
9090
  try {
8936
- const fileStat = await stat6(graphPath);
9091
+ const fileStat = await stat5(graphPath);
8937
9092
  const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
8938
9093
  if (ageHours > 24) needsBuild = true;
8939
9094
  } catch {
@@ -8944,7 +9099,7 @@ async function loadOrBuildGraph(vault) {
8944
9099
  if (buildResult.exitCode !== 0) return null;
8945
9100
  }
8946
9101
  try {
8947
- const raw = await readFile18(graphPath, "utf8");
9102
+ const raw = await readFile19(graphPath, "utf8");
8948
9103
  return JSON.parse(raw);
8949
9104
  } catch {
8950
9105
  return null;
@@ -8959,7 +9114,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8959
9114
  import { z } from "zod";
8960
9115
 
8961
9116
  // src/mcp/vault-resolve.ts
8962
- import { join as join30, resolve as resolve7 } from "path";
9117
+ import { join as join28, resolve as resolve7 } from "path";
8963
9118
 
8964
9119
  // src/mcp/allowlist.ts
8965
9120
  import { resolve as resolve6, sep as sep4 } from "path";
@@ -9021,7 +9176,7 @@ async function resolveMcpVault(input) {
9021
9176
  return ok({ vault: vaultPath, source });
9022
9177
  }
9023
9178
  function defaultGraphOut(vault) {
9024
- return join30(vault, ".skillwiki", "graph.json");
9179
+ return join28(vault, ".skillwiki", "graph.json");
9025
9180
  }
9026
9181
 
9027
9182
  // src/mcp/result-format.ts
@@ -9038,7 +9193,7 @@ function formatToolResult(payload) {
9038
9193
  // src/mcp/audit-log.ts
9039
9194
  import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
9040
9195
  import { homedir } from "os";
9041
- import { join as join31 } from "path";
9196
+ import { join as join29 } from "path";
9042
9197
  function auditEnabled() {
9043
9198
  const v = process.env.SKILLWIKI_MCP_AUDIT;
9044
9199
  if (v === "0" || v === "false") return false;
@@ -9050,7 +9205,7 @@ function auditSink() {
9050
9205
  function auditFilePath() {
9051
9206
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
9052
9207
  if (custom && custom.length > 0) return custom;
9053
- return join31(homedir(), ".skillwiki", "mcp-audit.jsonl");
9208
+ return join29(homedir(), ".skillwiki", "mcp-audit.jsonl");
9054
9209
  }
9055
9210
  function auditMcpToolCall(entry) {
9056
9211
  if (!auditEnabled()) return;
@@ -9060,7 +9215,7 @@ function auditMcpToolCall(entry) {
9060
9215
  return;
9061
9216
  }
9062
9217
  const path = auditFilePath();
9063
- mkdirSync5(join31(path, ".."), { recursive: true });
9218
+ mkdirSync5(join29(path, ".."), { recursive: true });
9064
9219
  appendFileSync(path, line, "utf8");
9065
9220
  }
9066
9221
  async function runMcpToolHandler(tool, input, fn) {
@@ -9280,8 +9435,8 @@ function registerMcpMutatingTools(server) {
9280
9435
  }
9281
9436
 
9282
9437
  // src/mcp/resources.ts
9283
- import { readFile as readFile20 } from "fs/promises";
9284
- import { join as join33 } from "path";
9438
+ import { readFile as readFile21 } from "fs/promises";
9439
+ import { join as join31 } from "path";
9285
9440
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
9286
9441
 
9287
9442
  // src/mcp/lint-bucket.ts
@@ -9409,8 +9564,8 @@ async function fetchQueryPreview(input) {
9409
9564
  }
9410
9565
 
9411
9566
  // src/mcp/graph-html.ts
9412
- import { readFile as readFile19 } from "fs/promises";
9413
- import { join as join32 } from "path";
9567
+ import { readFile as readFile20 } from "fs/promises";
9568
+ import { join as join30 } from "path";
9414
9569
  import { existsSync as existsSync16 } from "fs";
9415
9570
  var TYPE_COLORS = {
9416
9571
  entities: "#e74c3c",
@@ -9479,7 +9634,7 @@ ${nodeSvg}
9479
9634
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
9480
9635
  }
9481
9636
  async function fetchGraphHtmlReport(input) {
9482
- const graphPath = input.graphPath ?? join32(input.vault, ".skillwiki", "graph.json");
9637
+ const graphPath = input.graphPath ?? join30(input.vault, ".skillwiki", "graph.json");
9483
9638
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
9484
9639
  if (!existsSync16(graphPath)) {
9485
9640
  return {
@@ -9489,7 +9644,7 @@ async function fetchGraphHtmlReport(input) {
9489
9644
  }
9490
9645
  let raw;
9491
9646
  try {
9492
- raw = await readFile19(graphPath, "utf8");
9647
+ raw = await readFile20(graphPath, "utf8");
9493
9648
  } catch (e) {
9494
9649
  return {
9495
9650
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9553,7 +9708,7 @@ async function fetchStaleSummary(input) {
9553
9708
 
9554
9709
  // src/mcp/resources.ts
9555
9710
  async function readVaultFile(vault, rel) {
9556
- return readFile20(join33(vault, rel), "utf8");
9711
+ return readFile21(join31(vault, rel), "utf8");
9557
9712
  }
9558
9713
  async function tailLines(text, lines) {
9559
9714
  const parts = text.split(/\r?\n/);
@@ -9639,9 +9794,9 @@ function registerMcpResources(server) {
9639
9794
  if (!v.ok) {
9640
9795
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
9641
9796
  }
9642
- const path = join33(v.data.vault, ".skillwiki", "graph.json");
9797
+ const path = join31(v.data.vault, ".skillwiki", "graph.json");
9643
9798
  try {
9644
- const raw = await readFile20(path, "utf8");
9799
+ const raw = await readFile21(path, "utf8");
9645
9800
  const graph = JSON.parse(raw);
9646
9801
  const adjacency = graph.adjacency ?? {};
9647
9802
  const nodes = Object.keys(adjacency);
@@ -9954,11 +10109,6 @@ export {
9954
10109
  readLastOp,
9955
10110
  appendLastOp,
9956
10111
  clearLastOp,
9957
- eventPathFor,
9958
- validateLogEvent,
9959
- canonicalEventJson,
9960
- writeLogEvent,
9961
- readLogEvents,
9962
10112
  runLogAppend,
9963
10113
  renderIndexUpsert,
9964
10114
  upsertIndexEntry,
@@ -9974,7 +10124,6 @@ export {
9974
10124
  resolveInitTimePath,
9975
10125
  resolveRuntimePath,
9976
10126
  runOrphans,
9977
- extractCitationMarkers,
9978
10127
  runAudit,
9979
10128
  findPlugin,
9980
10129
  extractTaxonomy,
@@ -9986,6 +10135,8 @@ export {
9986
10135
  runIndexCheck,
9987
10136
  renderProjectIndex,
9988
10137
  runProjectIndex,
10138
+ planRawStructuralMove,
10139
+ applyRawStructuralMove,
9989
10140
  runStale,
9990
10141
  runPagesize,
9991
10142
  runLogRotate,
@@ -9996,8 +10147,10 @@ export {
9996
10147
  buildRemoteObjectPath,
9997
10148
  isValidRemoteDeleteCap,
9998
10149
  planAndMaybePruneRemoteObjects,
9999
- runDedup,
10000
10150
  safeWritePage,
10151
+ rewriteRawSourceReferences,
10152
+ snapshotMaintainedPageState,
10153
+ runDedup,
10001
10154
  runFrontmatterFix,
10002
10155
  fixPathTooLong,
10003
10156
  assessSourceIdentity,