skillwiki 0.10.21 → 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 (49) hide show
  1. package/dist/chunk-6ZDKTNLA.js +294 -0
  2. package/dist/{chunk-65Q5UGND.js → chunk-BASWDOQB.js} +2 -2
  3. package/dist/{chunk-Y6KRDGI2.js → chunk-C2DKFJFA.js} +6 -1
  4. package/dist/chunk-EQPU2BPM.js +468 -0
  5. package/dist/{chunk-GNS2ZV5P.js → chunk-PQG26AGJ.js} +1 -1
  6. package/dist/{chunk-I5JD3BQZ.js → chunk-QBZYEEBD.js} +13 -296
  7. package/dist/chunk-QNTBNNEL.js +616 -0
  8. package/dist/{chunk-UNPZDCWN.js → chunk-SYKSL3JQ.js} +860 -700
  9. package/dist/cli.js +2334 -273
  10. package/dist/{index-projection-HAXDEM2F.js → index-projection-FRWTZB5Y.js} +3 -2
  11. package/dist/{managed-write-preflight-CTXX2MHQ.js → managed-write-preflight-4LPVMP42.js} +3 -3
  12. package/dist/skillwiki-mcp.js +6 -4
  13. package/dist/sources-L2SQV63E.js +10 -0
  14. package/package.json +1 -1
  15. package/skills/.claude-plugin/plugin.json +1 -1
  16. package/skills/.codex-plugin/plugin.json +1 -1
  17. package/skills/README.md +13 -2
  18. package/skills/agents/wiki-add-task.md +2 -1
  19. package/skills/agents/wiki-archive.md +8 -9
  20. package/skills/agents/wiki-audit.md +2 -1
  21. package/skills/agents/wiki-lint.md +2 -1
  22. package/skills/agents/wiki-query.md +3 -1
  23. package/skills/agents/wiki-reingest.md +9 -8
  24. package/skills/package.json +1 -1
  25. package/skills/proj-decide/SKILL.md +35 -5
  26. package/skills/skills/proj-decide/SKILL.md +35 -5
  27. package/skills/skills/using-skillwiki/SKILL.md +19 -13
  28. package/skills/skills/wiki-add-task/SKILL.md +4 -3
  29. package/skills/skills/wiki-archive/SKILL.md +23 -18
  30. package/skills/skills/wiki-audit/SKILL.md +3 -1
  31. package/skills/skills/wiki-init/SKILL.md +12 -1
  32. package/skills/skills/wiki-lint/SKILL.md +2 -1
  33. package/skills/skills/wiki-query/SKILL.md +7 -0
  34. package/skills/skills/wiki-reingest/SKILL.md +12 -8
  35. package/skills/skills/wiki-remove/SKILL.md +22 -5
  36. package/skills/skills/wiki-sync/SKILL.md +39 -54
  37. package/skills/using-skillwiki/SKILL.md +19 -13
  38. package/skills/wiki-add-task/SKILL.md +4 -3
  39. package/skills/wiki-archive/SKILL.md +23 -18
  40. package/skills/wiki-audit/SKILL.md +3 -1
  41. package/skills/wiki-init/SKILL.md +12 -1
  42. package/skills/wiki-lint/SKILL.md +2 -1
  43. package/skills/wiki-query/SKILL.md +7 -0
  44. package/skills/wiki-reingest/SKILL.md +12 -8
  45. package/skills/wiki-remove/SKILL.md +22 -5
  46. package/skills/wiki-sync/SKILL.md +39 -54
  47. package/templates/SCHEMA.md +8 -5
  48. package/templates/web-clipper/llm-wiki-clippings.json +56 -0
  49. 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-I5JD3BQZ.js";
12
+ } from "./chunk-QBZYEEBD.js";
23
13
  import {
24
14
  CONFIG_KEYS,
25
15
  git,
@@ -34,7 +24,40 @@ import {
34
24
  satelliteGateFromFleetLoad,
35
25
  snapshotterAliasForLocalHost,
36
26
  writeDotenv
37
- } from "./chunk-GNS2ZV5P.js";
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,
@@ -49,12 +72,12 @@ import {
49
72
  ok,
50
73
  parseVaultSyncKeyValue,
51
74
  systemdPropertyFor
52
- } from "./chunk-Y6KRDGI2.js";
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,25 +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} -->`
203
+ `<!-- skillwiki-log-op:${operationId2} -->`,
204
+ `<!-- skillwiki-page-publish:${operationId2} -->`,
205
+ `<!-- skillwiki-project-page-publish:${operationId2} -->`
319
206
  ]);
320
207
  }
321
- function preferredMarker(operationId, eventKind) {
208
+ function preferredMarker(operationId2, eventKind) {
322
209
  if (!eventKind || eventKind === "page-publish") {
323
- return `<!-- skillwiki-page-publish:${operationId} -->`;
210
+ return `<!-- skillwiki-page-publish:${operationId2} -->`;
211
+ }
212
+ if (eventKind === "project-page-publish") {
213
+ return `<!-- skillwiki-project-page-publish:${operationId2} -->`;
324
214
  }
325
- return `<!-- skillwiki-log-op:${operationId} -->`;
215
+ return `<!-- skillwiki-log-op:${operationId2} -->`;
326
216
  }
327
217
  async function appendWhileLocked(logPath, content, markers, writeMarker) {
328
218
  let logText;
329
219
  try {
330
- logText = await readFile2(logPath, "utf8");
220
+ logText = await readFile(logPath, "utf8");
331
221
  } catch {
332
222
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
333
223
  }
@@ -366,7 +256,7 @@ ${appendedContent}
366
256
  }
367
257
  async function runLogAppend(input) {
368
258
  try {
369
- await stat(join4(input.vault, "SCHEMA.md"));
259
+ await stat(join3(input.vault, "SCHEMA.md"));
370
260
  } catch {
371
261
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
372
262
  }
@@ -439,7 +329,7 @@ async function runLogAppend(input) {
439
329
  return { exitCode: ExitCode.LOG_APPEND_LOCK_HELD, result: err("LOG_APPEND_LOCK_HELD", { vault: input.vault }) };
440
330
  }
441
331
  const lockHandle = acquired.data;
442
- const logPath = join4(input.vault, "log.md");
332
+ const logPath = join3(input.vault, "log.md");
443
333
  let outcome;
444
334
  let released;
445
335
  try {
@@ -490,7 +380,7 @@ async function runLogAppend(input) {
490
380
 
491
381
  // src/utils/sync-lock.ts
492
382
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
493
- import { join as join5 } from "path";
383
+ import { join as join4 } from "path";
494
384
  import { createHash, randomBytes as randomBytes2 } from "crypto";
495
385
  function getEnvSessionId() {
496
386
  if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
@@ -513,7 +403,7 @@ function getCliSessionId(cwd) {
513
403
  return `cli-${getCwdHash(cwd)}`;
514
404
  }
515
405
  function lockPath(vault) {
516
- return join5(vault, ".skillwiki", "sync.lock");
406
+ return join4(vault, ".skillwiki", "sync.lock");
517
407
  }
518
408
  function readLock(vault) {
519
409
  const path = lockPath(vault);
@@ -532,7 +422,7 @@ function isStale(lock, now) {
532
422
  }
533
423
  function acquireLock(vault, opts = {}) {
534
424
  const path = lockPath(vault);
535
- const dir = join5(vault, ".skillwiki");
425
+ const dir = join4(vault, ".skillwiki");
536
426
  if (!existsSync3(dir)) {
537
427
  mkdirSync3(dir, { recursive: true });
538
428
  }
@@ -617,7 +507,7 @@ function acquireOwnedSyncLock(vault, opts) {
617
507
  };
618
508
  const path = lockPath(vault);
619
509
  try {
620
- mkdirSync3(join5(vault, ".skillwiki"), { recursive: true });
510
+ mkdirSync3(join4(vault, ".skillwiki"), { recursive: true });
621
511
  } catch (error) {
622
512
  return err("WRITE_FAILED", { path, message: String(error) });
623
513
  }
@@ -649,12 +539,12 @@ function releaseOwnedSyncLock(handle) {
649
539
 
650
540
  // src/commands/validate.ts
651
541
  import { createHash as createHash2 } from "crypto";
652
- import { readFile as readFile4 } from "fs/promises";
542
+ import { readFile as readFile3 } from "fs/promises";
653
543
  import { resolve, relative, sep } from "path";
654
544
 
655
545
  // src/utils/index-entry.ts
656
- import { readFile as readFile3 } from "fs/promises";
657
- import { join as join6 } from "path";
546
+ import { readFile as readFile2 } from "fs/promises";
547
+ import { join as join5 } from "path";
658
548
  var TYPE_SECTION = {
659
549
  entity: "Entities",
660
550
  concept: "Concepts",
@@ -698,10 +588,10 @@ function renderIndexUpsert(text, input) {
698
588
  });
699
589
  }
700
590
  async function upsertIndexEntry(input) {
701
- const path = join6(input.vault, "index.md");
591
+ const path = join5(input.vault, "index.md");
702
592
  let before = "";
703
593
  try {
704
- before = await readFile3(path, "utf8");
594
+ before = await readFile2(path, "utf8");
705
595
  } catch {
706
596
  before = "";
707
597
  }
@@ -736,7 +626,7 @@ var SCHEMAS = {
736
626
  async function runValidate(input) {
737
627
  let text;
738
628
  try {
739
- text = await readFile4(input.file, "utf8");
629
+ text = await readFile3(input.file, "utf8");
740
630
  } catch {
741
631
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
742
632
  }
@@ -793,7 +683,7 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
793
683
  if (relPath.startsWith("..")) {
794
684
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { reason: `file ${input.file} is not inside vault ${input.vault}` }) };
795
685
  }
796
- 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");
797
687
  if (det.schema === "typed-knowledge" || det.schema === "meta") {
798
688
  const prepared = prepareTypedPage(text, relPath);
799
689
  if (!prepared.ok) {
@@ -848,7 +738,7 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
848
738
  const logged = await runLogAppend({
849
739
  vault: input.vault,
850
740
  content: `## [${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}] validate | added: ${relPath}`,
851
- operationId,
741
+ operationId: operationId2,
852
742
  strictLock: true
853
743
  });
854
744
  if (!logged.result.ok) {
@@ -876,7 +766,7 @@ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}` })
876
766
  }
877
767
 
878
768
  // src/commands/graph.ts
879
- import { writeFile, mkdir as mkdir2 } from "fs/promises";
769
+ import { writeFile, mkdir } from "fs/promises";
880
770
  import { dirname } from "path";
881
771
 
882
772
  // src/parsers/wikilinks.ts
@@ -1031,7 +921,7 @@ async function runGraphBuild(input) {
1031
921
  const adamicAdar = computeAdamicAdar(adjacency);
1032
922
  const edge_count = Object.values(adjacency).reduce((acc, arr) => acc + arr.length, 0);
1033
923
  try {
1034
- await mkdir2(dirname(input.out), { recursive: true });
924
+ await mkdir(dirname(input.out), { recursive: true });
1035
925
  await writeFile(input.out, JSON.stringify({ adjacency, adamicAdar }, null, 2));
1036
926
  } catch (e) {
1037
927
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
@@ -1075,7 +965,7 @@ function computeAdamicAdar(adj) {
1075
965
  }
1076
966
 
1077
967
  // src/utils/wiki-path.ts
1078
- import { join as join7 } from "path";
968
+ import { join as join6 } from "path";
1079
969
  async function resolveInitTimePath(input) {
1080
970
  const chain = [];
1081
971
  if (input.flag !== void 0 && input.flag.length > 0) {
@@ -1088,27 +978,27 @@ async function resolveInitTimePath(input) {
1088
978
  return { path: input.envValue, source: "env", ...input.explain ? { chain } : {} };
1089
979
  }
1090
980
  if (input.explain) chain.push({ source: "env", matched: false });
1091
- const sw = await parseDotenvFile(join7(input.home, ".skillwiki", ".env"));
981
+ const sw = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
1092
982
  if (sw.WIKI_PATH !== void 0) {
1093
983
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: true, value: sw.WIKI_PATH });
1094
984
  return { path: sw.WIKI_PATH, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} };
1095
985
  }
1096
986
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: false });
1097
- const hermes = await parseDotenvFile(join7(input.home, ".hermes", ".env"));
987
+ const hermes = await parseDotenvFile(join6(input.home, ".hermes", ".env"));
1098
988
  if (hermes.WIKI_PATH !== void 0) {
1099
989
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: true, value: hermes.WIKI_PATH });
1100
990
  return { path: hermes.WIKI_PATH, source: "hermes-dotenv", ...input.explain ? { chain } : {} };
1101
991
  }
1102
992
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: false });
1103
993
  if (input.cwd) {
1104
- const projCfg = await parseDotenvFile(join7(input.cwd, ".skillwiki", ".env"));
994
+ const projCfg = await parseDotenvFile(join6(input.cwd, ".skillwiki", ".env"));
1105
995
  if (projCfg.WIKI_PATH !== void 0) {
1106
996
  if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
1107
997
  return { path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} };
1108
998
  }
1109
999
  }
1110
1000
  if (input.explain) chain.push({ source: "project-dotenv", matched: false });
1111
- const fallback = join7(input.home, "wiki");
1001
+ const fallback = join6(input.home, "wiki");
1112
1002
  if (input.explain) chain.push({ source: "default", matched: true, value: fallback });
1113
1003
  return { path: fallback, source: "default", ...input.explain ? { chain } : {} };
1114
1004
  }
@@ -1119,7 +1009,7 @@ async function resolveRuntimePath(input) {
1119
1009
  return ok({ path: input.flag, source: "flag", ...input.explain ? { chain } : {} });
1120
1010
  }
1121
1011
  if (input.explain) chain.push({ source: "flag", matched: false });
1122
- const swGlobal = await parseDotenvFile(join7(input.home, ".skillwiki", ".env"));
1012
+ const swGlobal = await parseDotenvFile(join6(input.home, ".skillwiki", ".env"));
1123
1013
  const wikiName = input.wiki;
1124
1014
  if (wikiName !== void 0 && wikiName.length > 0) {
1125
1015
  if (wikiName.toLowerCase() === "default") {
@@ -1163,7 +1053,7 @@ async function resolveRuntimePath(input) {
1163
1053
  }
1164
1054
  if (input.explain) chain.push({ source: "env", matched: false });
1165
1055
  if (input.cwd) {
1166
- const projCfg = await parseDotenvFile(join7(input.cwd, ".skillwiki", ".env"));
1056
+ const projCfg = await parseDotenvFile(join6(input.cwd, ".skillwiki", ".env"));
1167
1057
  if (projCfg.WIKI_PATH !== void 0) {
1168
1058
  if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
1169
1059
  return ok({ path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} });
@@ -1283,156 +1173,12 @@ function simulateRemoval(adj, removed) {
1283
1173
  }
1284
1174
 
1285
1175
  // src/commands/audit.ts
1286
- import { readFile as readFile5, stat as stat3 } from "fs/promises";
1287
- import { dirname as dirname2, resolve as resolve2, join as join9 } from "path";
1288
-
1289
- // src/parsers/citations.ts
1290
- var FENCE2 = /```[\s\S]*?```/g;
1291
- var INLINE_CODE = /``[^`\n]+``|`[^`\n]+`/g;
1292
- var MARKER_RE = /\^\[(raw\/[^\]]+)\]/g;
1293
- var FRONTMATTER = /^---\n[\s\S]*?\n---\n?/;
1294
- function stripFences(body) {
1295
- return body.replace(FENCE2, "").replace(INLINE_CODE, "");
1296
- }
1297
- var TILDE_FENCE = /~~~[\s\S]*?~~~/g;
1298
- function stripFencedBlocks(body) {
1299
- return body.replace(FENCE2, "").replace(TILDE_FENCE, "");
1300
- }
1301
- function extractCitationMarkers(body) {
1302
- const stripped = stripFences(body);
1303
- const out = [];
1304
- let m;
1305
- while ((m = MARKER_RE.exec(stripped)) !== null) {
1306
- if (m[1] === "raw/...") continue;
1307
- out.push({ marker: m[0], target: m[1] });
1308
- }
1309
- return out;
1310
- }
1311
- function hasSourcesFooter(body) {
1312
- return /^## Sources\s*$/m.test(stripFencedBlocks(body));
1313
- }
1314
- function isLegacyCitationStyle(body) {
1315
- const markers = extractCitationMarkers(body);
1316
- if (markers.length === 0) return false;
1317
- if (!hasSourcesFooter(body)) return true;
1318
- const lines = stripFences(body.replace(FRONTMATTER, "")).split("\n");
1319
- let inSources = false;
1320
- let lastNonBlankWasTable = false;
1321
- for (const line of lines) {
1322
- if (/^## Sources\b/.test(line.trim())) {
1323
- inSources = true;
1324
- continue;
1325
- }
1326
- if (inSources) continue;
1327
- const matches = [...line.matchAll(MARKER_RE)];
1328
- if (matches.length === 0) {
1329
- if (line.trim().length > 0) lastNonBlankWasTable = /^\|/.test(line.trim());
1330
- continue;
1331
- }
1332
- const markerOnly = line.replace(MARKER_RE, "").trim();
1333
- if (markerOnly.length === 0 && !lastNonBlankWasTable) return true;
1334
- lastNonBlankWasTable = false;
1335
- const lastMatch = matches[matches.length - 1];
1336
- const afterLast = line.slice(lastMatch.index + lastMatch[0].length).replace(MARKER_RE, "").trim();
1337
- if (afterLast.length > 0) return true;
1338
- const beforeFirst = line.slice(0, matches[0].index).trim();
1339
- if (beforeFirst.length > 0 && !/[.!?]["'"]*\s*$/.test(beforeFirst)) return true;
1340
- }
1341
- return false;
1342
- }
1343
- function hasOrphanedCitations(body) {
1344
- const noFm = body.replace(FRONTMATTER, "");
1345
- const stripped = stripFences(noFm);
1346
- const lines = stripped.split("\n");
1347
- const rawLines = noFm.split("\n");
1348
- let inSources = false;
1349
- let sourcesEnded = false;
1350
- let sourcesStartLine = -1;
1351
- let lastNonBlankInSources = -1;
1352
- for (let i = 0; i < lines.length; i++) {
1353
- const line = lines[i];
1354
- const trimmed = line.trim();
1355
- if (/^## Sources\b/.test(trimmed)) {
1356
- inSources = true;
1357
- sourcesStartLine = i;
1358
- continue;
1359
- }
1360
- if (!inSources || sourcesEnded) continue;
1361
- if (trimmed.length === 0) {
1362
- if (lastNonBlankInSources >= 0) {
1363
- sourcesEnded = true;
1364
- }
1365
- continue;
1366
- }
1367
- const isListItem = /^\s*(?:[-*]|\d+\.)\s+/.test(line);
1368
- const hasMarker = /\^\[raw\//.test(line);
1369
- const hasBacktickRawPath = /`raw\/[^`]+`/.test(rawLines[i]);
1370
- if (isListItem && hasMarker) {
1371
- lastNonBlankInSources = i;
1372
- } else if (isListItem && hasBacktickRawPath) {
1373
- lastNonBlankInSources = i;
1374
- } else if (hasMarker && !isListItem) {
1375
- return true;
1376
- } else {
1377
- sourcesEnded = true;
1378
- }
1379
- }
1380
- if (sourcesStartLine === -1) return false;
1381
- if (sourcesEnded) {
1382
- const scanStart = Math.max(lastNonBlankInSources + 1, sourcesStartLine + 1);
1383
- for (let i = scanStart; i < lines.length; i++) {
1384
- if (/\^\[raw\//.test(lines[i])) {
1385
- return true;
1386
- }
1387
- }
1388
- }
1389
- return false;
1390
- }
1391
- function hasWikilinkCitations(body) {
1392
- const stripped = stripFences(body);
1393
- return /\[\[raw\/[^\]]+\]\]/.test(stripped);
1394
- }
1395
-
1396
- // src/utils/raw-source.ts
1397
- import { existsSync as existsSync4 } from "fs";
1398
- import { stat as stat2 } from "fs/promises";
1399
- import { join as join8 } from "path";
1400
- function normalizeRawSourceTarget(entry) {
1401
- let target = entry.trim().replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
1402
- target = target.replace(/^\^\[/, "").replace(/\]$/, "");
1403
- if (!target.startsWith("raw/") && !target.startsWith("_archive/raw/")) return null;
1404
- return target;
1405
- }
1406
- function rawSourceTargetCandidates(vault, target) {
1407
- const normalized = normalizeRawSourceTarget(target);
1408
- if (!normalized) return [];
1409
- const candidates = [join8(vault, normalized)];
1410
- if (!normalized.endsWith(".md")) candidates.push(join8(vault, `${normalized}.md`));
1411
- if (normalized.startsWith("raw/")) {
1412
- candidates.push(join8(vault, "_archive", normalized));
1413
- if (!normalized.endsWith(".md")) candidates.push(join8(vault, "_archive", `${normalized}.md`));
1414
- }
1415
- return [...new Set(candidates)];
1416
- }
1417
- function rawSourceTargetExistsSync(vault, target) {
1418
- return rawSourceTargetCandidates(vault, target).some((candidate) => existsSync4(candidate));
1419
- }
1420
- async function rawSourceTargetExists(vault, target) {
1421
- for (const candidate of rawSourceTargetCandidates(vault, target)) {
1422
- try {
1423
- await stat2(candidate);
1424
- return true;
1425
- } catch {
1426
- }
1427
- }
1428
- return false;
1429
- }
1430
-
1431
- // 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";
1432
1178
  async function runAudit(input) {
1433
1179
  let text;
1434
1180
  try {
1435
- text = await readFile5(input.file, "utf8");
1181
+ text = await readFile4(input.file, "utf8");
1436
1182
  } catch {
1437
1183
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
1438
1184
  }
@@ -1485,7 +1231,7 @@ async function findVaultRoot(start) {
1485
1231
  let cur = start;
1486
1232
  for (let i = 0; i < 20; i++) {
1487
1233
  try {
1488
- await stat3(join9(cur, "SCHEMA.md"));
1234
+ await stat2(join7(cur, "SCHEMA.md"));
1489
1235
  return cur;
1490
1236
  } catch {
1491
1237
  }
@@ -1577,8 +1323,8 @@ ${broken.map((b) => ` ${b.page}:[[${b.slug}]] (line ${b.line})`).join("\n")}` }
1577
1323
  }
1578
1324
 
1579
1325
  // src/commands/tag-audit.ts
1580
- import { readFile as readFile6 } from "fs/promises";
1581
- import { join as join10 } from "path";
1326
+ import { readFile as readFile5 } from "fs/promises";
1327
+ import { join as join8 } from "path";
1582
1328
 
1583
1329
  // src/parsers/taxonomy.ts
1584
1330
  import yaml from "js-yaml";
@@ -1603,12 +1349,12 @@ function parseTaxonomyDocument(schemaText) {
1603
1349
  const nextHeading = /^#{1,2}[ \t]+/m.exec(unboundedTail);
1604
1350
  const sectionEnd = nextHeading?.index === void 0 ? schemaText.length : afterHeading + nextHeading.index;
1605
1351
  const sectionText = schemaText.slice(afterHeading, sectionEnd);
1606
- const open2 = /^```yaml[ \t]*\r?$/m.exec(sectionText);
1607
- if (!open2 || open2.index === void 0) {
1352
+ const open = /^```yaml[ \t]*\r?$/m.exec(sectionText);
1353
+ if (!open || open.index === void 0) {
1608
1354
  return err("NO_TAXONOMY_BLOCK", { message: "Fenced YAML taxonomy block not found" });
1609
1355
  }
1610
- const openStart = afterHeading + open2.index;
1611
- const yamlStart = openStart + open2[0].length + 1;
1356
+ const openStart = afterHeading + open.index;
1357
+ const yamlStart = openStart + open[0].length + 1;
1612
1358
  const afterOpen = schemaText.slice(yamlStart, sectionEnd);
1613
1359
  const close = /^```[ \t]*\r?$/m.exec(afterOpen);
1614
1360
  if (!close || close.index === void 0) {
@@ -1731,7 +1477,7 @@ async function runTagAudit(input) {
1731
1477
  const scanResult = input.scan ? ok(input.scan) : await scanVault(input.vault);
1732
1478
  if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
1733
1479
  const scan = scanResult.data;
1734
- const schemaText = await readFile6(join10(input.vault, "SCHEMA.md"), "utf8");
1480
+ const schemaText = await readFile5(join8(input.vault, "SCHEMA.md"), "utf8");
1735
1481
  const tax = extractTaxonomy(schemaText);
1736
1482
  if (!tax.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: tax };
1737
1483
  const allowed = new Set(tax.data);
@@ -1763,8 +1509,8 @@ async function runTagAudit(input) {
1763
1509
  }
1764
1510
 
1765
1511
  // src/commands/index-check.ts
1766
- import { readFile as readFile7 } from "fs/promises";
1767
- import { join as join11 } from "path";
1512
+ import { readFile as readFile6 } from "fs/promises";
1513
+ import { join as join9 } from "path";
1768
1514
  function normalizeIndexTarget(raw) {
1769
1515
  return raw.replace(/\.md$/, "").replace(/^\.?\//, "");
1770
1516
  }
@@ -1776,7 +1522,7 @@ async function runIndexCheck(input) {
1776
1522
  }
1777
1523
  let indexText = "";
1778
1524
  try {
1779
- indexText = await readFile7(join11(input.vault, "index.md"), "utf8");
1525
+ indexText = await readFile6(join9(input.vault, "index.md"), "utf8");
1780
1526
  } catch {
1781
1527
  }
1782
1528
  const indexTargets = /* @__PURE__ */ new Set();
@@ -1830,20 +1576,20 @@ async function runIndexCheck(input) {
1830
1576
  }
1831
1577
 
1832
1578
  // src/commands/project-index.ts
1833
- import { readdir as readdir2, readFile as readFile8, mkdir as mkdir3 } from "fs/promises";
1834
- 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";
1835
1581
  var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
1836
1582
  var PROJECT_LOCAL_DIRS = ["requirements", "work", "architecture", "history"];
1837
1583
  async function scanMarkdownTree(rootAbs, rootRel) {
1838
1584
  const found = [];
1839
1585
  let entries;
1840
1586
  try {
1841
- entries = await readdir2(rootAbs, { withFileTypes: true });
1587
+ entries = await readdir(rootAbs, { withFileTypes: true });
1842
1588
  } catch {
1843
1589
  return found;
1844
1590
  }
1845
1591
  for (const entry of entries) {
1846
- const abs = join12(rootAbs, entry.name);
1592
+ const abs = join10(rootAbs, entry.name);
1847
1593
  const rel = `${rootRel}/${entry.name}`;
1848
1594
  if (entry.isDirectory()) {
1849
1595
  found.push(...await scanMarkdownTree(abs, rel));
@@ -1872,23 +1618,23 @@ function projectLocalType(slug, page, data) {
1872
1618
  return typeof data.type === "string" ? data.type : "project";
1873
1619
  }
1874
1620
  async function renderProjectIndex(vault, slug, opts = {}) {
1875
- const projectDir = join12(vault, "projects", slug);
1621
+ const projectDir = join10(vault, "projects", slug);
1876
1622
  try {
1877
- await readdir2(projectDir);
1623
+ await readdir(projectDir);
1878
1624
  } catch {
1879
1625
  return err("PROJECT_NOT_FOUND", { slug, path: projectDir });
1880
1626
  }
1881
1627
  const wikilinkPattern = `[[${slug}]]`;
1882
1628
  const entries = [];
1883
- const compoundDir = join12(vault, "projects", slug, "compound");
1629
+ const compoundDir = join10(vault, "projects", slug, "compound");
1884
1630
  try {
1885
- const compoundFiles = await readdir2(compoundDir, { withFileTypes: true });
1631
+ const compoundFiles = await readdir(compoundDir, { withFileTypes: true });
1886
1632
  for (const entry of compoundFiles) {
1887
1633
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1888
- const filePath = join12(compoundDir, entry.name);
1634
+ const filePath = join10(compoundDir, entry.name);
1889
1635
  let text;
1890
1636
  try {
1891
- text = await readFile8(filePath, "utf8");
1637
+ text = await readFile7(filePath, "utf8");
1892
1638
  } catch {
1893
1639
  continue;
1894
1640
  }
@@ -1905,16 +1651,16 @@ async function renderProjectIndex(vault, slug, opts = {}) {
1905
1651
  for (const dir of LAYER2_DIRS) {
1906
1652
  let files;
1907
1653
  try {
1908
- files = await readdir2(join12(vault, dir), { withFileTypes: true });
1654
+ files = await readdir(join10(vault, dir), { withFileTypes: true });
1909
1655
  } catch {
1910
1656
  continue;
1911
1657
  }
1912
1658
  for (const entry of files) {
1913
1659
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1914
- const filePath = join12(vault, dir, entry.name);
1660
+ const filePath = join10(vault, dir, entry.name);
1915
1661
  let text;
1916
1662
  try {
1917
- text = await readFile8(filePath, "utf8");
1663
+ text = await readFile7(filePath, "utf8");
1918
1664
  } catch {
1919
1665
  continue;
1920
1666
  }
@@ -1930,14 +1676,14 @@ async function renderProjectIndex(vault, slug, opts = {}) {
1930
1676
  }
1931
1677
  }
1932
1678
  for (const dir of PROJECT_LOCAL_DIRS) {
1933
- const rootAbs = join12(projectDir, dir);
1679
+ const rootAbs = join10(projectDir, dir);
1934
1680
  const rootRel = `projects/${slug}/${dir}`;
1935
1681
  const pages = await scanMarkdownTree(rootAbs, rootRel);
1936
1682
  for (const page of pages) {
1937
- const filePath = join12(vault, page);
1683
+ const filePath = join10(vault, page);
1938
1684
  let text;
1939
1685
  try {
1940
- text = await readFile8(filePath, "utf8");
1686
+ text = await readFile7(filePath, "utf8");
1941
1687
  } catch {
1942
1688
  continue;
1943
1689
  }
@@ -2010,7 +1756,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
2010
1756
  }
2011
1757
  async function runProjectIndex(input) {
2012
1758
  const slug = input.slug;
2013
- const projectDir = join12(input.vault, "projects", slug);
1759
+ const projectDir = join10(input.vault, "projects", slug);
2014
1760
  const rendered = await renderProjectIndex(input.vault, slug);
2015
1761
  if (!rendered.ok) {
2016
1762
  return {
@@ -2018,12 +1764,12 @@ async function runProjectIndex(input) {
2018
1764
  result: rendered
2019
1765
  };
2020
1766
  }
2021
- const indexPath = join12(projectDir, "knowledge.md");
1767
+ const indexPath = join10(projectDir, "knowledge.md");
2022
1768
  const entries = rendered.data.entries;
2023
1769
  let existing = false;
2024
1770
  let stale = false;
2025
1771
  try {
2026
- const existingText = await readFile8(indexPath, "utf8");
1772
+ const existingText = await readFile7(indexPath, "utf8");
2027
1773
  existing = true;
2028
1774
  const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
2029
1775
  const existingPages = new Set(existingEntries.map((l) => {
@@ -2036,7 +1782,7 @@ async function runProjectIndex(input) {
2036
1782
  }
2037
1783
  if (input.apply) {
2038
1784
  try {
2039
- await mkdir3(dirname3(indexPath), { recursive: true });
1785
+ await mkdir2(dirname3(indexPath), { recursive: true });
2040
1786
  } catch (e) {
2041
1787
  return {
2042
1788
  exitCode: ExitCode.WRITE_FAILED,
@@ -2068,8 +1814,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
2068
1814
  }
2069
1815
 
2070
1816
  // src/commands/stale.ts
2071
- import { readdir as readdir3, rename, mkdir as mkdir4, readFile as readFile9 } from "fs/promises";
2072
- 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";
2073
1819
 
2074
1820
  // src/parsers/expiry-annotations.ts
2075
1821
  var HEADING_RE = /^#{1,6}\s+(.+)$/;
@@ -2105,6 +1851,144 @@ function parseExpiryAnnotations(content, pagePath) {
2105
1851
  return annotations;
2106
1852
  }
2107
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
+
2108
1992
  // src/commands/stale.ts
2109
1993
  function daysSince(isoDate) {
2110
1994
  return Math.floor((Date.now() - Date.parse(isoDate)) / 864e5);
@@ -2118,10 +2002,10 @@ async function runStale(input) {
2118
2002
  const archived = [];
2119
2003
  const workDirs = /* @__PURE__ */ new Map();
2120
2004
  const workDirsBySlug = /* @__PURE__ */ new Map();
2121
- const projectsDir = join13(input.vault, "projects");
2005
+ const projectsDir = join11(input.vault, "projects");
2122
2006
  let projectSlugs = [];
2123
2007
  try {
2124
- 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);
2125
2009
  } catch {
2126
2010
  }
2127
2011
  if (input.project) {
@@ -2131,10 +2015,10 @@ async function runStale(input) {
2131
2015
  projectSlugs = [input.project];
2132
2016
  }
2133
2017
  for (const slug of projectSlugs) {
2134
- const workPath = join13(projectsDir, slug, "work");
2018
+ const workPath = join11(projectsDir, slug, "work");
2135
2019
  let entries;
2136
2020
  try {
2137
- entries = await readdir3(workPath, { withFileTypes: true });
2021
+ entries = await readdir2(workPath, { withFileTypes: true });
2138
2022
  } catch {
2139
2023
  continue;
2140
2024
  }
@@ -2142,11 +2026,11 @@ async function runStale(input) {
2142
2026
  for (const e of entries) {
2143
2027
  if (!e.isDirectory()) continue;
2144
2028
  const relDir = `projects/${slug}/work/${e.name}`;
2145
- const absDir = join13(workPath, e.name);
2029
+ const absDir = join11(workPath, e.name);
2146
2030
  let status = "";
2147
2031
  let files;
2148
2032
  try {
2149
- files = await readdir3(absDir);
2033
+ files = await readdir2(absDir);
2150
2034
  } catch {
2151
2035
  workDirs.set(relDir, "");
2152
2036
  slugDirs.set(e.name, "");
@@ -2155,7 +2039,7 @@ async function runStale(input) {
2155
2039
  for (const f of files) {
2156
2040
  if (!f.endsWith(".md")) continue;
2157
2041
  try {
2158
- const fm = extractFrontmatter(await readFile9(join13(absDir, f), "utf8"));
2042
+ const fm = extractFrontmatter(await readFile9(join11(absDir, f), "utf8"));
2159
2043
  if (fm.ok && typeof fm.data.status === "string") {
2160
2044
  status = fm.data.status;
2161
2045
  break;
@@ -2251,7 +2135,7 @@ async function runStale(input) {
2251
2135
  }
2252
2136
  }
2253
2137
  await mapWithConcurrency([...workDirs.keys()], vaultIoConcurrency(), async (relDir) => {
2254
- const specPath = join13(input.vault, relDir, "spec.md");
2138
+ const specPath = join11(input.vault, relDir, "spec.md");
2255
2139
  try {
2256
2140
  const specContent = await readFile9(specPath, "utf8");
2257
2141
  const specFm = extractFrontmatter(specContent);
@@ -2282,7 +2166,7 @@ async function runStale(input) {
2282
2166
  if (daysSince(dateStr) < input.days) continue;
2283
2167
  let files;
2284
2168
  try {
2285
- files = await readdir3(join13(input.vault, relDir));
2169
+ files = await readdir2(join11(input.vault, relDir));
2286
2170
  } catch {
2287
2171
  continue;
2288
2172
  }
@@ -2352,48 +2236,92 @@ async function runStale(input) {
2352
2236
  staleSections.push(...staleSectionResults.flat());
2353
2237
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2354
2238
  if (input.archive) {
2355
- const archiveDir = join13(input.vault, "_archive", today);
2356
- await mkdir4(archiveDir, { recursive: true });
2357
- const citedRawPaths = /* @__PURE__ */ new Set();
2358
- for (const page of scan.typedKnowledge) {
2359
- const text = await readPageCached(page, input.pageTextCache).catch(() => "");
2360
- for (const line of text.split("\n")) {
2361
- for (const m of line.matchAll(/\^\[(raw\/[^\]]+)\]/g)) {
2362
- citedRawPaths.add(m[1]);
2363
- }
2364
- for (const m of line.matchAll(/raw\/[^\s,\]"]+\.md/g)) {
2365
- citedRawPaths.add(m[0]);
2366
- }
2367
- }
2368
- }
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 = [];
2369
2254
  for (const t of staleTranscripts) {
2370
2255
  if (citedRawPaths.has(t.path) || citedRawPaths.has(t.path.replace(/\.md$/, ""))) continue;
2371
- const dest = join13(archiveDir, t.path.split("/").pop());
2372
- try {
2373
- await rename(join13(input.vault, t.path), dest);
2374
- archived.push(t.path);
2375
- } catch {
2376
- }
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);
2377
2303
  }
2378
2304
  for (const w of [...incompleteWorkItems, ...doneWorkItems]) {
2379
2305
  const parts = w.path.split("/");
2380
2306
  if (parts.length >= 4 && parts[0] === "projects") {
2381
2307
  const slug = parts[1];
2382
2308
  const itemName = parts[3];
2383
- const histDir = join13(input.vault, "projects", slug, "history", "archived-work");
2309
+ const histDir = join11(input.vault, "projects", slug, "history", "archived-work");
2384
2310
  await mkdir4(histDir, { recursive: true });
2385
- const dest = join13(histDir, itemName);
2311
+ const dest = join11(histDir, itemName);
2386
2312
  try {
2387
- await rename(join13(input.vault, w.path), dest);
2313
+ await rename(join11(input.vault, w.path), dest);
2388
2314
  archived.push(w.path);
2389
- } catch {
2315
+ } catch (error) {
2316
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: w.path, destination: dest, message: String(error) }) };
2390
2317
  }
2391
2318
  } else {
2392
- const dest = join13(archiveDir, w.path.replace(/\//g, "_"));
2319
+ const dest = join11(archiveDir, w.path.replace(/\//g, "_"));
2393
2320
  try {
2394
- await rename(join13(input.vault, w.path), dest);
2321
+ await rename(join11(input.vault, w.path), dest);
2395
2322
  archived.push(w.path);
2396
- } catch {
2323
+ } catch (error) {
2324
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: w.path, destination: dest, message: String(error) }) };
2397
2325
  }
2398
2326
  }
2399
2327
  }
@@ -2446,16 +2374,48 @@ async function runPagesize(input) {
2446
2374
  }
2447
2375
 
2448
2376
  // src/commands/log-rotate.ts
2449
- import { readFile as readFile10, rename as rename2, writeFile as writeFile2, stat as stat4 } from "fs/promises";
2450
- 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";
2451
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
+ }
2452
2412
  async function runLogRotate(input) {
2453
2413
  try {
2454
- await stat4(join14(input.vault, "SCHEMA.md"));
2414
+ await stat3(join12(input.vault, "SCHEMA.md"));
2455
2415
  } catch {
2456
2416
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
2457
2417
  }
2458
- const logPath = join14(input.vault, "log.md");
2418
+ const logPath = join12(input.vault, "log.md");
2459
2419
  let logText;
2460
2420
  try {
2461
2421
  logText = await readFile10(logPath, "utf8");
@@ -2467,37 +2427,56 @@ async function runLogRotate(input) {
2467
2427
  if (entries < input.threshold) {
2468
2428
  return { exitCode: ExitCode.OK, result: ok({ entries, threshold: input.threshold, rotated: false, humanHint: `${entries}/${input.threshold} entries \u2014 no rotation needed` }) };
2469
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);
2470
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)`;
2471
2436
  return {
2472
2437
  exitCode: ExitCode.LOG_ROTATE_NEEDED,
2473
- 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 })
2474
2439
  };
2475
2440
  }
2476
- const newestYear = matches[matches.length - 1][1];
2477
- const rotatedName = `log-${newestYear}.md`;
2478
- const rotatedPath = join14(input.vault, rotatedName);
2441
+ let archivedExistingTo;
2479
2442
  try {
2443
+ archivedExistingTo = await sidelineExistingYearLog(input.vault, rotatedPath, newestYear);
2480
2444
  await rename2(logPath, rotatedPath);
2481
2445
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2446
+ const sidelineNote = archivedExistingTo ? `
2447
+ - Prior ${rotatedName} preserved as ${archivedExistingTo}
2448
+ ` : "";
2482
2449
  const fresh = `# Vault Log
2483
2450
 
2484
2451
  Chronological action log. Newest entries last. Skill writes append entries; lint may rotate.
2485
2452
 
2486
2453
  ## [${today}] rotate | Log rotated from ${entries} entries
2487
2454
 
2488
- - Previous log moved to ${rotatedName}
2489
- `;
2455
+ - Previous log moved to ${rotatedName}${sidelineNote}`;
2490
2456
  await writeFile2(logPath, fresh, "utf8");
2491
2457
  } catch (e) {
2492
2458
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
2493
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}`;
2494
2463
  appendLastOp(input.vault, {
2495
2464
  operation: "log-rotate",
2496
- summary: `rotated ${entries} entries to ${rotatedName}`,
2497
- files: ["log.md", rotatedName],
2465
+ summary,
2466
+ files,
2498
2467
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2499
2468
  });
2500
- 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
+ };
2501
2480
  }
2502
2481
 
2503
2482
  // src/commands/topic-map-check.ts
@@ -2521,12 +2500,12 @@ async function runTopicMapCheck(input) {
2521
2500
 
2522
2501
  // src/commands/index-link-format.ts
2523
2502
  import { readFile as readFile11 } from "fs/promises";
2524
- import { join as join15 } from "path";
2503
+ import { join as join13 } from "path";
2525
2504
  var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
2526
2505
  async function runIndexLinkFormat(input) {
2527
2506
  let text = "";
2528
2507
  try {
2529
- text = await readFile11(join15(input.vault, "index.md"), "utf8");
2508
+ text = await readFile11(join13(input.vault, "index.md"), "utf8");
2530
2509
  } catch {
2531
2510
  }
2532
2511
  const markdown_links = [];
@@ -2539,9 +2518,9 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
2539
2518
  }
2540
2519
 
2541
2520
  // src/commands/dedup.ts
2542
- import { createHash as createHash3 } from "crypto";
2543
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
2544
- 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";
2545
2524
 
2546
2525
  // src/utils/rclone.ts
2547
2526
  import { execFile } from "child_process";
@@ -2590,6 +2569,140 @@ async function defaultRcloneRunner(args) {
2590
2569
  }
2591
2570
  }
2592
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
+
2593
2706
  // src/commands/dedup.ts
2594
2707
  async function runDedup(input) {
2595
2708
  if (input.canonicalPolicy && input.canonicalPolicy !== "scan-order" && input.canonicalPolicy !== "stable-path") {
@@ -2610,7 +2723,11 @@ async function runDedup(input) {
2610
2723
  }
2611
2724
  const hashMap = /* @__PURE__ */ new Map();
2612
2725
  let totalFiles = 0;
2613
- 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) => {
2614
2731
  const fm = extractFrontmatter(await readPageCached(raw, input.pageTextCache));
2615
2732
  if (!fm.ok) return null;
2616
2733
  const sha = typeof fm.data.sha256 === "string" ? fm.data.sha256 : null;
@@ -2625,77 +2742,101 @@ async function runDedup(input) {
2625
2742
  else hashMap.set(entry.sha, [entry.relPath]);
2626
2743
  }
2627
2744
  const canonicalPolicy = input.canonicalPolicy ?? "stable-path";
2628
- 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) }));
2629
2746
  const rewired = [];
2630
2747
  const removed = [];
2748
+ const relocated = [];
2631
2749
  const unsafe = [];
2632
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
+ }
2633
2755
  const manifest = safeEntries.length > 0 ? {
2634
2756
  version: 1,
2635
2757
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2636
2758
  vault: resolve3(input.vault),
2637
2759
  entries: safeEntries
2638
2760
  } : void 0;
2639
- const remote = await planAndMaybePruneRemote(input, safeEntries);
2640
- if (!remote.ok) {
2641
- return {
2642
- exitCode: remote.error === "SYNC_PUSH_FAILED" ? ExitCode.SYNC_PUSH_FAILED : ExitCode.USAGE,
2643
- result: remote
2644
- };
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 }) };
2645
2782
  }
2646
2783
  if (input.manifestOut && manifest) {
2647
2784
  try {
2648
- mkdirSync4(dirname4(input.manifestOut), { recursive: true });
2785
+ mkdirSync4(dirname5(input.manifestOut), { recursive: true });
2649
2786
  writeFileSync4(input.manifestOut, `${JSON.stringify(manifest, null, 2)}
2650
2787
  `, "utf-8");
2651
2788
  } catch (e) {
2652
2789
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: input.manifestOut, message: String(e) }) };
2653
2790
  }
2654
2791
  }
2655
- if (input.apply && safeEntries.length > 0) {
2792
+ if (input.apply && structuralPlans.length > 0) {
2656
2793
  const replacements = /* @__PURE__ */ new Map();
2657
- for (const entry of safeEntries) {
2658
- for (const duplicate of entry.duplicates) {
2659
- replacements.set(duplicate, entry.canonical);
2660
- }
2661
- }
2794
+ for (const plan of structuralPlans) replacements.set(plan.source, plan.canonical);
2795
+ const pendingWrites = [];
2662
2796
  for (const page of scan.allMarkdown.filter((p) => !p.relPath.startsWith("raw/"))) {
2663
- const text = readFileSync4(join16(input.vault, page.relPath), "utf-8");
2797
+ const text = readFileSync4(join14(input.vault, page.relPath), "utf-8");
2664
2798
  let updated = text;
2665
2799
  let changed = false;
2666
2800
  for (const [oldPath, newPath] of replacements) {
2667
- const oldMarker = `^[${oldPath}]`;
2668
- const newMarker = `^[${newPath}]`;
2669
- if (updated.includes(oldMarker)) {
2670
- updated = updated.replaceAll(oldMarker, newMarker);
2671
- changed = true;
2672
- }
2673
- const oldFm = `- "^[${oldPath}]"`;
2674
- const newFm = `- "^[${newPath}]"`;
2675
- if (updated.includes(oldFm)) {
2676
- updated = updated.replaceAll(oldFm, newFm);
2677
- changed = true;
2678
- }
2801
+ const rewritten = rewriteRawSourceReferences(updated, oldPath, newPath);
2802
+ updated = rewritten.text;
2803
+ changed = changed || rewritten.changed;
2679
2804
  }
2680
2805
  if (changed) {
2681
- writeFileSync4(join16(input.vault, page.relPath), updated);
2806
+ pendingWrites.push({ page: page.relPath, text: updated });
2682
2807
  rewired.push(page.relPath);
2683
2808
  }
2684
2809
  }
2685
- for (const oldPath of replacements.keys()) {
2686
- const fullPath = join16(input.vault, oldPath);
2687
- try {
2688
- unlinkSync4(fullPath);
2689
- removed.push(oldPath);
2690
- } catch {
2691
- }
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 };
2692
2826
  }
2693
2827
  }
2694
- 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)) {
2695
2836
  appendLastOp(input.vault, {
2696
2837
  operation: "dedup",
2697
- summary: `rewired ${rewired.length} pages, removed ${removed.length} duplicates`,
2698
- 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])],
2699
2840
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2700
2841
  });
2701
2842
  }
@@ -2706,7 +2847,7 @@ async function runDedup(input) {
2706
2847
  for (const d of duplicates) hintLines.push(` ${d.sha256.slice(0, 12)}... \u2192 ${d.files.join(", ")}`);
2707
2848
  if (input.apply) {
2708
2849
  hintLines.push(`rewired: ${rewired.length} pages`);
2709
- hintLines.push(`removed: ${removed.length} raw files`);
2850
+ hintLines.push(`preserved duplicates: ${relocated.length} raw files`);
2710
2851
  }
2711
2852
  if (unsafe.length > 0) hintLines.push(`unsafe: ${unsafe.length} groups skipped`);
2712
2853
  if (remote.data.plannedDeletes.length > 0) {
@@ -2726,10 +2867,21 @@ async function runDedup(input) {
2726
2867
  remote: remote.data,
2727
2868
  rewired,
2728
2869
  removed,
2870
+ relocated,
2871
+ ...approvalToken ? { approval_token: approvalToken } : {},
2729
2872
  humanHint: hintLines.join("\n")
2730
2873
  })
2731
2874
  };
2732
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
+ }
2733
2885
  function orderFiles(files, policy) {
2734
2886
  if (policy === "scan-order") return [...files];
2735
2887
  return [...files].sort(compareStableRawPath);
@@ -2777,10 +2929,13 @@ function buildSafeEntries(vault, duplicates, unsafe) {
2777
2929
  return entries;
2778
2930
  }
2779
2931
  function hashRawBody(vault, relPath) {
2780
- 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) {
2781
2936
  const split = splitFrontmatter(text);
2782
2937
  const body = split.ok ? split.data.body : text;
2783
- return createHash3("sha256").update(body).digest("hex");
2938
+ return createHash5("sha256").update(body).digest("hex");
2784
2939
  }
2785
2940
  function readManifest(path) {
2786
2941
  try {
@@ -2793,69 +2948,50 @@ function readManifest(path) {
2793
2948
  return err("INVALID_FRONTMATTER", { path, message: String(e) });
2794
2949
  }
2795
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
+ }
2796
2989
  async function planAndMaybePruneRemote(input, entries) {
2797
2990
  const remoteRoot = normalizeRemoteRoot(input.remote);
2798
2991
  const plannedDeletes = remoteRoot ? entries.flatMap((entry) => entry.duplicates.map((path) => `${remoteRoot}/${path}`)) : [];
2799
2992
  return planAndMaybePruneRemoteObjects(plannedDeletes, { ...input, defaultMaxDeletes: 50 });
2800
2993
  }
2801
2994
 
2802
- // src/utils/safe-write.ts
2803
- import { readFile as readFile12, writeFile as writeFile3 } from "fs/promises";
2804
- var DEFAULT_MIN_BODY_RATIO = 0.5;
2805
- var DEFAULT_MIN_OLD_BODY_BYTES = 200;
2806
- function bodyBytes(text) {
2807
- const split = splitFrontmatter(text);
2808
- if (!split.ok) return Buffer.byteLength(text, "utf8");
2809
- return Buffer.byteLength(split.data.body, "utf8");
2810
- }
2811
- async function readIfExists(absPath) {
2812
- try {
2813
- return await readFile12(absPath, "utf8");
2814
- } catch (e) {
2815
- if (e.code === "ENOENT") return null;
2816
- throw e;
2817
- }
2818
- }
2819
- async function safeWritePage(absPath, newContent, opts = {}) {
2820
- const minRatio = opts.minBodyRatio === void 0 ? DEFAULT_MIN_BODY_RATIO : opts.minBodyRatio;
2821
- const minOldBytes = opts.minOldBodyBytes ?? DEFAULT_MIN_OLD_BODY_BYTES;
2822
- let oldContent;
2823
- try {
2824
- oldContent = await readIfExists(absPath);
2825
- } catch (e) {
2826
- return err("WRITE_FAILED", { path: absPath, phase: "read-existing", message: String(e) });
2827
- }
2828
- const isNew = oldContent === null;
2829
- const oldBodyBytes = isNew ? 0 : bodyBytes(oldContent);
2830
- const newBodyBytes = bodyBytes(newContent);
2831
- const bodyRatio = oldBodyBytes > 0 ? newBodyBytes / oldBodyBytes : null;
2832
- let guardSkippedSmall = false;
2833
- if (!isNew && minRatio !== null && bodyRatio !== null && bodyRatio < minRatio) {
2834
- if (oldBodyBytes < minOldBytes) {
2835
- guardSkippedSmall = true;
2836
- } else {
2837
- return err("BODY_TRUNCATION_GUARD", {
2838
- path: absPath,
2839
- oldBodyBytes,
2840
- newBodyBytes,
2841
- bodyRatio,
2842
- minBodyRatio: minRatio,
2843
- 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."
2844
- });
2845
- }
2846
- }
2847
- const written = await atomicWriteText(absPath, newContent);
2848
- if (!written.ok) return written;
2849
- return ok({
2850
- changed: written.data.changed,
2851
- isNew,
2852
- oldBodyBytes,
2853
- newBodyBytes,
2854
- bodyRatio,
2855
- guardSkippedSmall
2856
- });
2857
- }
2858
-
2859
2995
  // src/commands/frontmatter-fix.ts
2860
2996
  function isoToday() {
2861
2997
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -2934,9 +3070,8 @@ ${newBody}`;
2934
3070
 
2935
3071
  // src/commands/lint.ts
2936
3072
  import { existsSync as existsSync6 } from "fs";
2937
- import { readFile as readFile14, readdir as readdir4 } from "fs/promises";
2938
- import { createHash as createHash5 } from "crypto";
2939
- 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";
2940
3075
 
2941
3076
  // src/commands/sparse-community.ts
2942
3077
  async function runSparseCommunity(input) {
@@ -2952,7 +3087,7 @@ async function runSparseCommunity(input) {
2952
3087
  }
2953
3088
 
2954
3089
  // src/commands/raw-body-dedup.ts
2955
- import { createHash as createHash4 } from "crypto";
3090
+ import { createHash as createHash6 } from "crypto";
2956
3091
  async function runRawBodyDedup(vault, scan, pageTextCache) {
2957
3092
  const scanResult = scan ? ok(scan) : await scanVault(vault);
2958
3093
  if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
@@ -2961,7 +3096,7 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2961
3096
  const text = await readPageCached(raw, pageTextCache);
2962
3097
  const split = splitFrontmatter(text);
2963
3098
  if (!split.ok) return null;
2964
- const bodyHash = createHash4("sha256").update(split.data.body).digest("hex");
3099
+ const bodyHash = createHash6("sha256").update(split.data.body).digest("hex");
2965
3100
  const fm = extractFrontmatter(text);
2966
3101
  let fmSha256 = null;
2967
3102
  if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
@@ -2994,8 +3129,8 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
2994
3129
 
2995
3130
  // src/commands/path-too-long.ts
2996
3131
  import { existsSync as existsSync5 } from "fs";
2997
- import { mkdir as mkdir5, readFile as readFile13, rename as rename3, unlink } from "fs/promises";
2998
- 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";
2999
3134
  var MAX_PATH_LENGTH = 240;
3000
3135
  var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
3001
3136
  async function runPathTooLong(input) {
@@ -3021,6 +3156,10 @@ async function fixPathTooLong(input) {
3021
3156
  const fixed = [];
3022
3157
  const unresolved = [];
3023
3158
  for (const violation of violations) {
3159
+ if (violation.relPath.startsWith("raw/")) {
3160
+ unresolved.push(violation.relPath);
3161
+ continue;
3162
+ }
3024
3163
  const target = await resolveFixTarget(input.vault, violation.relPath, violation.suggestedRelPath, maxFixLength);
3025
3164
  if (!target || target.relPath === violation.relPath || target.relPath.length > maxFixLength) {
3026
3165
  unresolved.push(violation.relPath);
@@ -3028,10 +3167,10 @@ async function fixPathTooLong(input) {
3028
3167
  }
3029
3168
  try {
3030
3169
  if (target.mode === "dedupe") {
3031
- await unlink(join17(input.vault, violation.relPath));
3170
+ await unlink2(join15(input.vault, violation.relPath));
3032
3171
  } else {
3033
- await mkdir5(dirname5(join17(input.vault, target.relPath)), { recursive: true });
3034
- 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));
3035
3174
  }
3036
3175
  fixed.push({ from: violation.relPath, to: target.relPath });
3037
3176
  } catch {
@@ -3045,7 +3184,7 @@ async function fixPathTooLong(input) {
3045
3184
  for (const page of afterScan.data.allMarkdown) {
3046
3185
  if (!shouldRewriteReferences(page.relPath)) continue;
3047
3186
  try {
3048
- const original = await readFile13(page.absPath, "utf8");
3187
+ const original = await readFile14(page.absPath, "utf8");
3049
3188
  let updated = original;
3050
3189
  for (const fix of fixed) {
3051
3190
  updated = replacePathReferences(updated, fix.from, fix.to);
@@ -3108,9 +3247,9 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
3108
3247
  async function resolveFixTarget(vault, original, preferred, maxLength) {
3109
3248
  for (const candidate of candidateRelPaths(preferred, maxLength)) {
3110
3249
  if (candidate === original || candidate.length > maxLength) continue;
3111
- const candidatePath = join17(vault, candidate);
3250
+ const candidatePath = join15(vault, candidate);
3112
3251
  if (!existsSync5(candidatePath)) return { relPath: candidate, mode: "rename" };
3113
- if (await hasSameContent(join17(vault, original), candidatePath)) {
3252
+ if (await hasSameContent(join15(vault, original), candidatePath)) {
3114
3253
  return { relPath: candidate, mode: "dedupe" };
3115
3254
  }
3116
3255
  }
@@ -3119,8 +3258,8 @@ async function resolveFixTarget(vault, original, preferred, maxLength) {
3119
3258
  function candidateRelPaths(preferred, maxLength) {
3120
3259
  const candidates = [preferred];
3121
3260
  if (preferred.length > maxLength) return candidates;
3122
- const dir = posix.dirname(preferred) === "." ? "" : posix.dirname(preferred);
3123
- const filename = posix.basename(preferred);
3261
+ const dir = posix2.dirname(preferred) === "." ? "" : posix2.dirname(preferred);
3262
+ const filename = posix2.basename(preferred);
3124
3263
  const ext = filename.endsWith(".md") ? ".md" : "";
3125
3264
  const base = ext ? filename.slice(0, -3) : filename;
3126
3265
  const dirPrefix = dir ? `${dir}/` : "";
@@ -3134,7 +3273,7 @@ function candidateRelPaths(preferred, maxLength) {
3134
3273
  }
3135
3274
  async function hasSameContent(a, b) {
3136
3275
  try {
3137
- const [left, right] = await Promise.all([readFile13(a), readFile13(b)]);
3276
+ const [left, right] = await Promise.all([readFile14(a), readFile14(b)]);
3138
3277
  return left.equals(right);
3139
3278
  } catch {
3140
3279
  return false;
@@ -3147,8 +3286,8 @@ function shouldRewriteReferences(relPath) {
3147
3286
  }
3148
3287
  function replacePathReferences(content, oldRelPath, newRelPath) {
3149
3288
  let updated = content.replaceAll(oldRelPath, newRelPath);
3150
- const oldStem = posix.basename(oldRelPath).replace(/\.md$/, "");
3151
- const newStem = posix.basename(newRelPath).replace(/\.md$/, "");
3289
+ const oldStem = posix2.basename(oldRelPath).replace(/\.md$/, "");
3290
+ const newStem = posix2.basename(newRelPath).replace(/\.md$/, "");
3152
3291
  if (oldStem !== newStem) {
3153
3292
  const oldStemEscaped = oldStem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3154
3293
  const stemWikilinkRe = new RegExp(`\\[\\[${oldStemEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
@@ -3177,7 +3316,8 @@ function buildCliSurface() {
3177
3316
  program.command("graph");
3178
3317
  program.command("canvas");
3179
3318
  program.command("overlap").option("--wiki <name>");
3180
- 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");
3181
3321
  program.command("orphans").option("--wiki <name>");
3182
3322
  program.command("audit");
3183
3323
  program.command("install").option("--target <dir>").option("--dry-run").option("--skills-root <dir>").option("--symlink");
@@ -3189,7 +3329,7 @@ function buildCliSurface() {
3189
3329
  program.command("index-check").option("--wiki <name>");
3190
3330
  program.command("index-link-format").option("--wiki <name>");
3191
3331
  program.command("topic-map-check").option("--threshold <n>").option("--wiki <name>");
3192
- 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>");
3193
3333
  program.command("claim").option("--project <slug>").option("--slug <slug>").option("--wiki <name>");
3194
3334
  program.command("pagesize").option("--lines <n>").option("--wiki <name>");
3195
3335
  program.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
@@ -3204,10 +3344,10 @@ function buildCliSurface() {
3204
3344
  program.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
3205
3345
  program.command("doctor").option("--check-snapshotter");
3206
3346
  program.command("status").option("--wiki <name>");
3207
- 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>");
3208
3348
  program.command("remove").option("--wiki <name>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--reason <text>");
3209
3349
  program.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
3210
- 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>");
3211
3351
  program.command("migrate-citations").option("--dry-run").option("--wiki <name>");
3212
3352
  program.command("frontmatter-fix").option("--dry-run").option("--wiki <name>");
3213
3353
  program.command("update").option("--tag <tag>");
@@ -3227,10 +3367,15 @@ function buildCliSurface() {
3227
3367
  program.command("ingest").requiredOption("--vault <path>").requiredOption("--type <type>").requiredOption("--title <title>").option("--tags <csv>").option("--provenance <provenance>").option("--dry-run");
3228
3368
  program.command("fleet");
3229
3369
  program.command("page");
3370
+ program.command("project-page");
3230
3371
  program.command("write-preflight").option("--command <name>").option("--dirty-threshold <n>").option("--skip-dirty").option("--prior-artifact-file <path>").option("--prior-artifact-text <text>").option("--consecutive-no-decision <n>").option("--no-decision-threshold <n>").option("--human-allow").option("--mission-kind <kind>").option("--skip-mission").option("--project <slug>").option("--capture-day <date>").option("--capture-budget <n>").option("--severity <level>").option("--skip-budget").option("--checks <list>").option("--wiki <name>");
3231
3372
  program.command("mcp");
3232
3373
  const graphCmd = program.commands.find((c) => c.name() === "graph");
3233
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>");
3234
3379
  const canvasCmd = program.commands.find((c) => c.name() === "canvas");
3235
3380
  canvasCmd.command("generate").option("--graph-path <path>").option("--wiki <name>");
3236
3381
  const configCmd = program.commands.find((c) => c.name() === "config");
@@ -3245,7 +3390,9 @@ function buildCliSurface() {
3245
3390
  const tagCmd = program.commands.find((c) => c.name() === "tag");
3246
3391
  tagCmd.command("reconcile").requiredOption("--page <path>").option("--from <path>").option("--tags <csv>").option("--reason <text>").option("--write").option("--wiki <name>");
3247
3392
  const pageCmd = program.commands.find((c) => c.name() === "page");
3248
- pageCmd.command("publish").requiredOption("--target <path>").option("--log-note <text>").option("--write").option("--wiki <name>");
3393
+ pageCmd.command("publish").requiredOption("--target <path>").option("--log-note <text>").option("--write").option("--approve <token>").option("--wiki <name>");
3394
+ const projectPageCmd = program.commands.find((c) => c.name() === "project-page");
3395
+ projectPageCmd.command("publish").requiredOption("--project <slug>").requiredOption("--target <path>").option("--log-note <text>").option("--write").option("--approve <token>").option("--wiki <name>");
3249
3396
  const logCmd = program.commands.find((c) => c.name() === "log");
3250
3397
  logCmd.command("materialize").option("--write").option("--wiki <name>");
3251
3398
  logCmd.command("migrate-legacy").option("--write").option("--converge-vault <dir>").option("--wiki <name>");
@@ -3569,17 +3716,6 @@ function readMirrorHintLines(vault) {
3569
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"
3570
3717
  ];
3571
3718
  }
3572
- function recomputeRawSha256IfPresent(content) {
3573
- const split = splitFrontmatter(content);
3574
- if (!split.ok) return content;
3575
- if (!/^sha256:\s*[0-9a-f]{64}$/m.test(split.data.rawFrontmatter)) return content;
3576
- const sha256 = createHash5("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
3577
- const rawFrontmatter = split.data.rawFrontmatter.replace(/^sha256:\s*[0-9a-f]{64}$/m, `sha256: ${sha256}`);
3578
- return `---
3579
- ${rawFrontmatter}
3580
- ---
3581
- ${split.data.body}`;
3582
- }
3583
3719
  function appendLintFixLastOp(vault, fixed) {
3584
3720
  if (fixed.length === 0) return;
3585
3721
  appendLastOp(vault, {
@@ -3618,10 +3754,10 @@ function summarizeLintOutput(output, examplesLimit = 3) {
3618
3754
  };
3619
3755
  }
3620
3756
  async function walkMarkdownFiles(absDir, vaultRoot) {
3621
- const entries = await readdir4(absDir, { withFileTypes: true });
3757
+ const entries = await readdir3(absDir, { withFileTypes: true });
3622
3758
  const pages = [];
3623
3759
  for (const entry of entries) {
3624
- const absPath = join18(absDir, entry.name);
3760
+ const absPath = join16(absDir, entry.name);
3625
3761
  if (entry.isDirectory()) {
3626
3762
  if (entry.name === ".git" || entry.name === "node_modules") continue;
3627
3763
  pages.push(...await walkMarkdownFiles(absPath, vaultRoot));
@@ -3632,12 +3768,12 @@ async function walkMarkdownFiles(absDir, vaultRoot) {
3632
3768
  return pages;
3633
3769
  }
3634
3770
  async function collectCliRefsPages(vault) {
3635
- if (!existsSync6(join18(vault, "SCHEMA.md"))) {
3771
+ if (!existsSync6(join16(vault, "SCHEMA.md"))) {
3636
3772
  return err("VAULT_PATH_INVALID", { root: vault, reason: "SCHEMA.md missing" });
3637
3773
  }
3638
3774
  const pages = [];
3639
3775
  for (const dir of CLI_REFS_TYPED_DIRS) {
3640
- const absDir = join18(vault, dir);
3776
+ const absDir = join16(vault, dir);
3641
3777
  if (!existsSync6(absDir)) continue;
3642
3778
  pages.push(...await walkMarkdownFiles(absDir, vault));
3643
3779
  }
@@ -3757,9 +3893,13 @@ async function applyFileSourceUrlFix(input, scan, fileSourceUrlFlags, fileSource
3757
3893
  if (!input.fix || fileSourceUrlFrontmatterFlags.size === 0) return fileSourceUrlFlags;
3758
3894
  const fileFixed = [];
3759
3895
  for (const relPath of fileSourceUrlFrontmatterFlags) {
3896
+ if (relPath.startsWith("raw/")) {
3897
+ unresolved.push(relPath);
3898
+ continue;
3899
+ }
3760
3900
  try {
3761
3901
  const absPath = `${input.vault}/${relPath}`;
3762
- const raw = await readFile14(absPath, "utf8");
3902
+ const raw = await readFile15(absPath, "utf8");
3763
3903
  const parts = raw.split("---", 3);
3764
3904
  if (parts.length < 3) {
3765
3905
  unresolved.push(relPath);
@@ -4152,8 +4292,8 @@ async function runLint(input) {
4152
4292
  const readKnowledgeContent = (slug) => {
4153
4293
  const existing = knowledgeContentCache.get(slug);
4154
4294
  if (existing) return existing;
4155
- const knowledgePath = join18(lintVault, "projects", slug, "knowledge.md");
4156
- 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);
4157
4297
  knowledgeContentCache.set(slug, pending);
4158
4298
  return pending;
4159
4299
  };
@@ -4230,8 +4370,11 @@ async function runLint(input) {
4230
4370
  const raw = await readPage(page);
4231
4371
  const redacted = redactSensitiveContent(raw, { file: page.relPath });
4232
4372
  if (!redacted.changed) continue;
4233
- const next = recomputeRawSha256IfPresent(redacted.text);
4234
- 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 });
4235
4378
  if (!w.ok) {
4236
4379
  unresolved.push(page.relPath);
4237
4380
  continue;
@@ -4259,7 +4402,7 @@ async function runLint(input) {
4259
4402
  for (const relPath of legacyPages) {
4260
4403
  try {
4261
4404
  const absPath = `${input.vault}/${relPath}`;
4262
- const raw = await readFile14(absPath, "utf8");
4405
+ const raw = await readFile15(absPath, "utf8");
4263
4406
  const split = splitFrontmatter(raw);
4264
4407
  if (!split.ok) {
4265
4408
  unresolved.push(relPath);
@@ -4358,7 +4501,7 @@ ${newBody}`;
4358
4501
  for (const relPath of noOverview) {
4359
4502
  try {
4360
4503
  const absPath = `${input.vault}/${relPath}`;
4361
- const raw = await readFile14(absPath, "utf8");
4504
+ const raw = await readFile15(absPath, "utf8");
4362
4505
  const split = splitFrontmatter(raw);
4363
4506
  if (!split.ok) {
4364
4507
  unresolved.push(relPath);
@@ -4399,7 +4542,7 @@ ${trimmedBody}`;
4399
4542
  for (const relPath of missingTldrFlags) {
4400
4543
  try {
4401
4544
  const absPath = `${input.vault}/${relPath}`;
4402
- const raw = await readFile14(absPath, "utf8");
4545
+ const raw = await readFile15(absPath, "utf8");
4403
4546
  const split = splitFrontmatter(raw);
4404
4547
  if (!split.ok) {
4405
4548
  unresolved.push(relPath);
@@ -4449,7 +4592,7 @@ ${lines.join("\n")}`;
4449
4592
  for (const relPath of wikilinkCitationFlags) {
4450
4593
  try {
4451
4594
  const absPath = `${input.vault}/${relPath}`;
4452
- const raw = await readFile14(absPath, "utf8");
4595
+ const raw = await readFile15(absPath, "utf8");
4453
4596
  const split = splitFrontmatter(raw);
4454
4597
  if (!split.ok) {
4455
4598
  unresolved.push(relPath);
@@ -4554,6 +4697,11 @@ ${newBody}`;
4554
4697
  const invalidItems = buckets.frontmatter_yaml_invalid;
4555
4698
  const remaining = [];
4556
4699
  for (const item of invalidItems) {
4700
+ if (item.path.startsWith("raw/")) {
4701
+ unresolved.push(item.path);
4702
+ remaining.push(item);
4703
+ continue;
4704
+ }
4557
4705
  const page = scan.allMarkdown.find((p) => p.relPath === item.path);
4558
4706
  if (!page) {
4559
4707
  unresolved.push(item.path);
@@ -4790,14 +4938,14 @@ async function runSyncLintDelta(input) {
4790
4938
  }
4791
4939
 
4792
4940
  // src/commands/config.ts
4793
- import { readFile as readFile15 } from "fs/promises";
4941
+ import { readFile as readFile16 } from "fs/promises";
4794
4942
  import { existsSync as existsSync7 } from "fs";
4795
- import { join as join19 } from "path";
4943
+ import { join as join17 } from "path";
4796
4944
  function validateKey(key) {
4797
4945
  return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key) || isVaultSyncKey(key);
4798
4946
  }
4799
4947
  function configPath(home) {
4800
- return join19(home, ".skillwiki", ".env");
4948
+ return join17(home, ".skillwiki", ".env");
4801
4949
  }
4802
4950
  async function runConfigGet(input) {
4803
4951
  if (!validateKey(input.key)) {
@@ -4821,7 +4969,7 @@ async function runConfigSet(input) {
4821
4969
  try {
4822
4970
  let originalContent;
4823
4971
  try {
4824
- originalContent = await readFile15(filePath, "utf8");
4972
+ originalContent = await readFile16(filePath, "utf8");
4825
4973
  } catch {
4826
4974
  }
4827
4975
  const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
@@ -4858,19 +5006,19 @@ async function runConfigPath(input) {
4858
5006
 
4859
5007
  // src/commands/doctor.ts
4860
5008
  import { existsSync as existsSync13, lstatSync, readlinkSync, readdirSync as readdirSync3, statSync as statSync2, readFileSync as readFileSync10 } from "fs";
4861
- import { join as join25, resolve as resolve5 } from "path";
5009
+ import { join as join23, resolve as resolve5 } from "path";
4862
5010
  import { execSync as execSync2 } from "child_process";
4863
5011
  import { platform as platform2 } from "os";
4864
5012
 
4865
5013
  // src/utils/plugin-registry.ts
4866
5014
  import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync5 } from "fs";
4867
- import { join as join20 } from "path";
4868
- var REGISTRY_PATH = join20(".claude", "plugins", "installed_plugins.json");
4869
- 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");
4870
5018
  var PLUGIN_KEY = "skillwiki@llm-wiki";
4871
5019
  function readInstalledPlugins(home) {
4872
5020
  try {
4873
- const raw = readFileSync5(join20(home, REGISTRY_PATH), "utf8");
5021
+ const raw = readFileSync5(join18(home, REGISTRY_PATH), "utf8");
4874
5022
  return JSON.parse(raw);
4875
5023
  } catch {
4876
5024
  return null;
@@ -4906,7 +5054,7 @@ function findPluginInstallations(home, key = PLUGIN_KEY) {
4906
5054
  function findCodexPlugin(home, key, pluginName, marketplace) {
4907
5055
  const config = readCodexPluginConfig(home, key, marketplace);
4908
5056
  if (!config?.enabled) return null;
4909
- const cacheRoot = join20(home, ".codex", "plugins", "cache", marketplace, pluginName);
5057
+ const cacheRoot = join18(home, ".codex", "plugins", "cache", marketplace, pluginName);
4910
5058
  if (!existsSync8(cacheRoot)) return null;
4911
5059
  let versions;
4912
5060
  try {
@@ -4922,7 +5070,7 @@ function findCodexPlugin(home, key, pluginName, marketplace) {
4922
5070
  key,
4923
5071
  pluginName,
4924
5072
  marketplace,
4925
- installPath: join20(cacheRoot, version),
5073
+ installPath: join18(cacheRoot, version),
4926
5074
  version,
4927
5075
  sourceType: config.sourceType,
4928
5076
  source: config.source
@@ -4939,7 +5087,7 @@ function parsePluginKey(key) {
4939
5087
  function readCodexPluginConfig(home, key, marketplace) {
4940
5088
  let raw;
4941
5089
  try {
4942
- raw = readFileSync5(join20(home, CODEX_CONFIG_PATH), "utf8");
5090
+ raw = readFileSync5(join18(home, CODEX_CONFIG_PATH), "utf8");
4943
5091
  } catch {
4944
5092
  return null;
4945
5093
  }
@@ -4982,7 +5130,7 @@ function parseTomlScalar(rawValue) {
4982
5130
 
4983
5131
  // src/utils/conflict-markers.ts
4984
5132
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
4985
- import { join as join21 } from "path";
5133
+ import { join as join19 } from "path";
4986
5134
  function scanConflictMarkerBlocksInText(relPath, text) {
4987
5135
  const findings = [];
4988
5136
  const lines = text.split(/\r?\n/);
@@ -5033,7 +5181,7 @@ function walkMarkdownFiles2(root, dir, rel, out) {
5033
5181
  for (const entry of entries) {
5034
5182
  if (entry.isDirectory()) {
5035
5183
  if (PRUNE_DIRS.has(entry.name)) continue;
5036
- 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);
5037
5185
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
5038
5186
  out.push(rel ? `${rel}/${entry.name}` : entry.name);
5039
5187
  }
@@ -5047,7 +5195,7 @@ function scanVaultConflictMarkers(vaultRoot) {
5047
5195
  for (const rel of relPaths) {
5048
5196
  let text;
5049
5197
  try {
5050
- text = readFileSync6(join21(vaultRoot, rel), "utf8");
5198
+ text = readFileSync6(join19(vaultRoot, rel), "utf8");
5051
5199
  } catch {
5052
5200
  continue;
5053
5201
  }
@@ -5058,7 +5206,7 @@ function scanVaultConflictMarkers(vaultRoot) {
5058
5206
 
5059
5207
  // src/utils/remote-health.ts
5060
5208
  import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
5061
- import { join as join22 } from "path";
5209
+ import { join as join20 } from "path";
5062
5210
  import { execFileSync } from "child_process";
5063
5211
  var REMOTE_PROBE_TIMEOUT_MS = 3e3;
5064
5212
  var defaultExec = (file, args, cwd) => execFileSync(file, args, {
@@ -5069,7 +5217,7 @@ var defaultExec = (file, args, cwd) => execFileSync(file, args, {
5069
5217
  }).trim();
5070
5218
  function readWikiS3RemoteConfigured(home) {
5071
5219
  try {
5072
- const content = readFileSync7(join22(home, ".skillwiki", ".env"), "utf8");
5220
+ const content = readFileSync7(join20(home, ".skillwiki", ".env"), "utf8");
5073
5221
  for (const line of content.split(/\r?\n/)) {
5074
5222
  const trimmed = line.trim();
5075
5223
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -5094,7 +5242,7 @@ function resolveWikiS3Remote(input) {
5094
5242
  return readWikiS3RemoteConfigured(input.home);
5095
5243
  }
5096
5244
  function probeGithubReachability(vaultPath, exec = defaultExec) {
5097
- if (!existsSync10(join22(vaultPath, ".git"))) return "unknown";
5245
+ if (!existsSync10(join20(vaultPath, ".git"))) return "unknown";
5098
5246
  try {
5099
5247
  exec("git", ["remote", "get-url", "origin"], vaultPath);
5100
5248
  } catch {
@@ -5160,10 +5308,10 @@ function probeRemoteHealth(input) {
5160
5308
 
5161
5309
  // src/utils/satellite-run-health.ts
5162
5310
  import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
5163
- import { join as join23 } from "path";
5311
+ import { join as join21 } from "path";
5164
5312
  var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
5165
5313
  function satelliteLatestRunPath(vault) {
5166
- return join23(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5314
+ return join21(vault, ".skillwiki", "agent-memory-trends", "latest-run.json");
5167
5315
  }
5168
5316
  function isFailedRunStatus(status) {
5169
5317
  return status === "fail" || status === "failure";
@@ -5216,8 +5364,8 @@ function evaluateSatelliteRunHealth(vault, now) {
5216
5364
  // src/utils/s3-mount-health.ts
5217
5365
  import { execSync } from "child_process";
5218
5366
  import { platform } from "os";
5219
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5, readFileSync as readFile16 } from "fs";
5220
- 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";
5221
5369
  var OS = platform();
5222
5370
  function findRcloneMountPid() {
5223
5371
  try {
@@ -5375,7 +5523,7 @@ function detectFuseMount(vaultPath) {
5375
5523
  return null;
5376
5524
  }
5377
5525
  function writeTest(dir) {
5378
- const testFile = join24(dir, `.doctor-write-test-${process.pid}.tmp`);
5526
+ const testFile = join22(dir, `.doctor-write-test-${process.pid}.tmp`);
5379
5527
  const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
5380
5528
  const start = Date.now();
5381
5529
  try {
@@ -5386,24 +5534,24 @@ function writeTest(dir) {
5386
5534
  const writeMs = Date.now() - start;
5387
5535
  const readStart = Date.now();
5388
5536
  try {
5389
- const back = readFile16(testFile, "utf8");
5537
+ const back = readFile17(testFile, "utf8");
5390
5538
  const readMs = Date.now() - readStart;
5391
5539
  if (back !== payload) {
5392
5540
  try {
5393
- unlinkSync5(testFile);
5541
+ unlinkSync4(testFile);
5394
5542
  } catch {
5395
5543
  }
5396
5544
  return { success: false, writeMs, readMs, size: Buffer.byteLength(payload, "utf8"), error: "content mismatch \u2014 wrote and read-back differ" };
5397
5545
  }
5398
5546
  } catch (e) {
5399
5547
  try {
5400
- unlinkSync5(testFile);
5548
+ unlinkSync4(testFile);
5401
5549
  } catch {
5402
5550
  }
5403
5551
  return { success: false, writeMs, readMs: Date.now() - readStart, size: 0, error: `read failed: ${e.message}` };
5404
5552
  }
5405
5553
  try {
5406
- unlinkSync5(testFile);
5554
+ unlinkSync4(testFile);
5407
5555
  } catch {
5408
5556
  }
5409
5557
  return { success: true, writeMs, readMs: Date.now() - readStart, size: Buffer.byteLength(payload, "utf8") };
@@ -5475,12 +5623,12 @@ function detectCliChannels(argv, home) {
5475
5623
  }
5476
5624
  const plugin = findPlugin(home);
5477
5625
  if (plugin) {
5478
- const pluginBin = join25(plugin.installPath, "bin", "skillwiki");
5626
+ const pluginBin = join23(plugin.installPath, "bin", "skillwiki");
5479
5627
  if (existsSync13(pluginBin)) {
5480
5628
  channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
5481
5629
  }
5482
5630
  }
5483
- const installBin = join25(home, ".claude", "skills", "bin", "skillwiki");
5631
+ const installBin = join23(home, ".claude", "skills", "bin", "skillwiki");
5484
5632
  if (existsSync13(installBin)) {
5485
5633
  channels.push({ name: "install", path: installBin, isDevLink: false });
5486
5634
  }
@@ -5570,9 +5718,9 @@ function checkVaultStructure(resolvedPath) {
5570
5718
  return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
5571
5719
  }
5572
5720
  const missing = [];
5573
- if (!existsSync13(join25(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5721
+ if (!existsSync13(join23(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
5574
5722
  for (const dir of ["raw", "entities", "concepts", "meta"]) {
5575
- if (!existsSync13(join25(resolvedPath, dir))) missing.push(dir + "/");
5723
+ if (!existsSync13(join23(resolvedPath, dir))) missing.push(dir + "/");
5576
5724
  }
5577
5725
  if (missing.length === 0) {
5578
5726
  return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
@@ -5580,7 +5728,7 @@ function checkVaultStructure(resolvedPath) {
5580
5728
  return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
5581
5729
  }
5582
5730
  function checkSkillsInstalled(home, cwd) {
5583
- const srcDir = cwd ? join25(cwd, "packages", "skills") : void 0;
5731
+ const srcDir = cwd ? join23(cwd, "packages", "skills") : void 0;
5584
5732
  if (srcDir && existsSync13(srcDir)) {
5585
5733
  const found = findInstalledSkillMd(srcDir);
5586
5734
  if (found.length > 0) {
@@ -5594,7 +5742,7 @@ function checkSkillsInstalled(home, cwd) {
5594
5742
  return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
5595
5743
  }
5596
5744
  }
5597
- const skillsDir = join25(home, ".claude", "skills");
5745
+ const skillsDir = join23(home, ".claude", "skills");
5598
5746
  if (existsSync13(skillsDir)) {
5599
5747
  const found = findInstalledSkillMd(skillsDir);
5600
5748
  if (found.length > 0) {
@@ -5605,10 +5753,10 @@ function checkSkillsInstalled(home, cwd) {
5605
5753
  }
5606
5754
  function checkDuplicateSkills(home) {
5607
5755
  const plugin = findPlugin(home);
5608
- const skillsDir = join25(home, ".claude", "skills");
5756
+ const skillsDir = join23(home, ".claude", "skills");
5609
5757
  const agentSkillDirs = [
5610
- { label: "~/.codex/skills/", path: join25(home, ".codex", "skills") },
5611
- { 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") }
5612
5760
  ];
5613
5761
  if (!plugin) {
5614
5762
  return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
@@ -5711,7 +5859,7 @@ async function checkProfiles(home) {
5711
5859
  }
5712
5860
  async function checkProjectLocalOverride(cwd) {
5713
5861
  const dir = cwd ?? process.cwd();
5714
- const envPath = join25(dir, ".skillwiki", ".env");
5862
+ const envPath = join23(dir, ".skillwiki", ".env");
5715
5863
  if (existsSync13(envPath)) {
5716
5864
  return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
5717
5865
  }
@@ -5721,7 +5869,7 @@ function checkVaultGitRemote(resolvedPath) {
5721
5869
  if (resolvedPath === void 0) {
5722
5870
  return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
5723
5871
  }
5724
- if (!existsSync13(join25(resolvedPath, ".git"))) {
5872
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5725
5873
  return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
5726
5874
  }
5727
5875
  try {
@@ -5744,9 +5892,9 @@ function checkObsidianTemplates(resolvedPath) {
5744
5892
  return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
5745
5893
  }
5746
5894
  const missing = [];
5747
- if (!existsSync13(join25(resolvedPath, "_Templates"))) missing.push("_Templates/");
5748
- if (!existsSync13(join25(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
5749
- 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");
5750
5898
  if (missing.length === 0) {
5751
5899
  return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
5752
5900
  }
@@ -5756,7 +5904,7 @@ function checkDotStoreClean(resolvedPath) {
5756
5904
  if (resolvedPath === void 0) {
5757
5905
  return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
5758
5906
  }
5759
- const rawDir = join25(resolvedPath, "raw");
5907
+ const rawDir = join23(resolvedPath, "raw");
5760
5908
  if (!existsSync13(rawDir)) {
5761
5909
  return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
5762
5910
  }
@@ -5772,7 +5920,7 @@ function checkDotStoreClean(resolvedPath) {
5772
5920
  if (entry.name === ".DS_Store") {
5773
5921
  found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
5774
5922
  } else if (entry.isDirectory()) {
5775
- walk(join25(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5923
+ walk(join23(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
5776
5924
  }
5777
5925
  }
5778
5926
  })(rawDir, "");
@@ -5803,7 +5951,7 @@ function checkSyncLastPush(resolvedPath) {
5803
5951
  if (resolvedPath === void 0) {
5804
5952
  return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
5805
5953
  }
5806
- if (!existsSync13(join25(resolvedPath, ".git"))) {
5954
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5807
5955
  return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
5808
5956
  }
5809
5957
  let timestamp;
@@ -5852,7 +6000,7 @@ function checkVaultGitDirty(resolvedPath) {
5852
6000
  if (resolvedPath === void 0) {
5853
6001
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
5854
6002
  }
5855
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6003
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5856
6004
  return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
5857
6005
  }
5858
6006
  try {
@@ -5921,7 +6069,7 @@ function remoteMainHash(resolvedPath) {
5921
6069
  }
5922
6070
  function checkStaleRemoteMain(resolvedPath) {
5923
6071
  if (resolvedPath === void 0) return void 0;
5924
- if (!existsSync13(join25(resolvedPath, ".git"))) return void 0;
6072
+ if (!existsSync13(join23(resolvedPath, ".git"))) return void 0;
5925
6073
  const localOrigin = gitRefHash(resolvedPath, "origin/main");
5926
6074
  if (!localOrigin) return void 0;
5927
6075
  const remoteMain = remoteMainHash(resolvedPath);
@@ -5937,7 +6085,7 @@ function checkVaultLocalGit(resolvedPath) {
5937
6085
  if (resolvedPath === void 0) {
5938
6086
  return check("warn", "vault_local_git", "Vault local git", "Cannot check \u2014 WIKI_PATH not resolved");
5939
6087
  }
5940
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6088
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5941
6089
  return check("warn", "vault_local_git", "Vault local git", "Not a git repository - sync features unavailable");
5942
6090
  }
5943
6091
  try {
@@ -5956,7 +6104,7 @@ function checkVaultGithubRemote(resolvedPath, exec) {
5956
6104
  if (resolvedPath === void 0) {
5957
6105
  return check("pass", "vault_github_remote", "Vault GitHub remote", "No vault path \u2014 check skipped");
5958
6106
  }
5959
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6107
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
5960
6108
  return check("pass", "vault_github_remote", "Vault GitHub remote", "No git repo \u2014 check skipped");
5961
6109
  }
5962
6110
  const state = probeGithubReachability(resolvedPath, exec);
@@ -6000,7 +6148,7 @@ function checkVaultPromotionLag(resolvedPath) {
6000
6148
  if (resolvedPath === void 0) {
6001
6149
  return check("pass", "vault_promotion_lag", "Vault promotion lag", "No vault path \u2014 check skipped");
6002
6150
  }
6003
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6151
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
6004
6152
  return check("pass", "vault_promotion_lag", "Vault promotion lag", "No git repo \u2014 check skipped");
6005
6153
  }
6006
6154
  try {
@@ -6027,7 +6175,7 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
6027
6175
  if (resolvedPath === void 0) {
6028
6176
  return check("pass", id, label, "No vault path \u2014 check skipped");
6029
6177
  }
6030
- if (!existsSync13(join25(resolvedPath, ".git"))) {
6178
+ if (!existsSync13(join23(resolvedPath, ".git"))) {
6031
6179
  return check("pass", id, label, "No git repo \u2014 check skipped");
6032
6180
  }
6033
6181
  if (!hasOriginMain(resolvedPath)) {
@@ -6144,11 +6292,11 @@ async function checkFleetIdentity(input) {
6144
6292
  }
6145
6293
  function pullLogPaths(home) {
6146
6294
  const paths = platform2() === "darwin" ? [
6147
- join25(home, "Library", "Logs", "wiki-pull.log"),
6148
- 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")
6149
6297
  ] : [
6150
- join25(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
6151
- 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")
6152
6300
  ];
6153
6301
  return [...new Set(paths)];
6154
6302
  }
@@ -6188,7 +6336,7 @@ function checkS3MountPerf(resolvedPath) {
6188
6336
  return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
6189
6337
  }
6190
6338
  const mountPoint = fuse.mountPoint;
6191
- const conceptsDir = join25(resolvedPath, "concepts");
6339
+ const conceptsDir = join23(resolvedPath, "concepts");
6192
6340
  if (!existsSync13(conceptsDir)) {
6193
6341
  return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
6194
6342
  }
@@ -6371,7 +6519,7 @@ function checkWriteTest(resolvedPath) {
6371
6519
  if (!fuse) {
6372
6520
  return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
6373
6521
  }
6374
- const conceptsDir = join25(resolvedPath, "concepts");
6522
+ const conceptsDir = join23(resolvedPath, "concepts");
6375
6523
  if (!existsSync13(conceptsDir)) {
6376
6524
  return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
6377
6525
  }
@@ -6473,7 +6621,7 @@ function checkVaultSyncPullHelper(home, env) {
6473
6621
  );
6474
6622
  }
6475
6623
  function checkVaultSyncReviewRequiredJournals(vaultPath) {
6476
- if (!vaultPath || !existsSync13(join25(vaultPath, ".git"))) {
6624
+ if (!vaultPath || !existsSync13(join23(vaultPath, ".git"))) {
6477
6625
  return check("pass", "vault_sync_review_required_journals", "Review-required journals", "No git vault \u2014 check skipped");
6478
6626
  }
6479
6627
  try {
@@ -6494,7 +6642,7 @@ function checkVaultSyncReviewRequiredJournals(vaultPath) {
6494
6642
  }
6495
6643
  function readVaultSyncConfig(home) {
6496
6644
  try {
6497
- const content = readFileSync10(join25(home, ".skillwiki", ".env"), "utf8");
6645
+ const content = readFileSync10(join23(home, ".skillwiki", ".env"), "utf8");
6498
6646
  let installed = false;
6499
6647
  let role;
6500
6648
  let serviceScope;
@@ -6623,7 +6771,7 @@ function snapshotterHealthChecks(scope, logDir, env) {
6623
6771
  let completionOutcome = "unknown";
6624
6772
  const logRecords = fixture ? fixture.log_records : (() => {
6625
6773
  try {
6626
- const content = readFileSync10(join25(logDir, "wiki-snapshot.log"), "utf8");
6774
+ const content = readFileSync10(join23(logDir, "wiki-snapshot.log"), "utf8");
6627
6775
  return content.split(/\r?\n/).filter(Boolean);
6628
6776
  } catch {
6629
6777
  return [];
@@ -6693,10 +6841,10 @@ function vaultSyncChecks(input) {
6693
6841
  ];
6694
6842
  }
6695
6843
  const isMac = os === "darwin";
6696
- const logDir = input.logDir ?? (isMac ? join25(home, "Library", "Logs") : join25(home, ".local", "state", "vault-sync", "log"));
6697
- const shareDir = input.shareDir ?? (isMac ? join25(home, "Library", "Application Support", "vault-sync", "bin") : join25(home, ".local", "share", "vault-sync", "bin"));
6698
- const filterPath = input.filterPath ?? join25(home, ".config", "rclone", "wiki-push-filters.txt");
6699
- 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");
6700
6848
  const legacySnapshotPath = "/root/.hermes/scripts/wiki-snapshot-v3.sh";
6701
6849
  const snapshotPath = input.snapshotScriptPath ?? (existsSync13(packagedSnapshotPath) ? packagedSnapshotPath : legacySnapshotPath);
6702
6850
  if (input.vaultSyncRole === "snapshotter") {
@@ -6752,7 +6900,7 @@ function vaultSyncChecks(input) {
6752
6900
  }
6753
6901
  return [c12, ...healthChecks, cFetch2, c42, c52];
6754
6902
  }
6755
- const pushScriptPath = join25(shareDir, "wiki-push.sh");
6903
+ const pushScriptPath = join23(shareDir, "wiki-push.sh");
6756
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`);
6757
6905
  let c2;
6758
6906
  try {
@@ -6804,7 +6952,7 @@ function vaultSyncChecks(input) {
6804
6952
  "Scheduler check failed \u2014 run vault-sync-install"
6805
6953
  );
6806
6954
  }
6807
- const logFile = join25(logDir, "wiki-push.log");
6955
+ const logFile = join23(logDir, "wiki-push.log");
6808
6956
  let c3;
6809
6957
  try {
6810
6958
  const logContent = readFileSync10(logFile, "utf8");
@@ -6875,7 +7023,7 @@ function vaultSyncChecks(input) {
6875
7023
  `Log directory not found at ${logDir}`
6876
7024
  );
6877
7025
  }
6878
- const fetchLogFile = join25(logDir, "wiki-fetch.log");
7026
+ const fetchLogFile = join23(logDir, "wiki-fetch.log");
6879
7027
  let cFetch;
6880
7028
  try {
6881
7029
  const logContent = readFileSync10(fetchLogFile, "utf8");
@@ -7019,15 +7167,15 @@ function findSkillMd(dir) {
7019
7167
  }
7020
7168
  for (const entry of entries) {
7021
7169
  if (entry.isFile() && entry.name === "SKILL.md") {
7022
- results.push(join25(dir, entry.name));
7170
+ results.push(join23(dir, entry.name));
7023
7171
  } else if (entry.isDirectory()) {
7024
- results.push(...findSkillMd(join25(dir, entry.name)));
7172
+ results.push(...findSkillMd(join23(dir, entry.name)));
7025
7173
  }
7026
7174
  }
7027
7175
  return results;
7028
7176
  }
7029
7177
  function findInstalledSkillMd(dir) {
7030
- const directSkills = findSkillNames(dir).map((name) => join25(dir, name, "SKILL.md"));
7178
+ const directSkills = findSkillNames(dir).map((name) => join23(dir, name, "SKILL.md"));
7031
7179
  return directSkills.length > 0 ? directSkills : findSkillMd(dir);
7032
7180
  }
7033
7181
  function findSkillNames(dir) {
@@ -7039,7 +7187,7 @@ function findSkillNames(dir) {
7039
7187
  return results;
7040
7188
  }
7041
7189
  for (const entry of entries) {
7042
- if (entry.isDirectory() && existsSync13(join25(dir, entry.name, "SKILL.md"))) {
7190
+ if (entry.isDirectory() && existsSync13(join23(dir, entry.name, "SKILL.md"))) {
7043
7191
  results.push(entry.name);
7044
7192
  }
7045
7193
  }
@@ -7087,7 +7235,7 @@ async function vaultMetrics(resolvedPath) {
7087
7235
  }
7088
7236
  let logLines = 0;
7089
7237
  try {
7090
- logLines = readFileSync10(join25(scanRoot, "log.md"), "utf8").split("\n").length;
7238
+ logLines = readFileSync10(join23(scanRoot, "log.md"), "utf8").split("\n").length;
7091
7239
  } catch {
7092
7240
  }
7093
7241
  return [
@@ -7222,7 +7370,7 @@ function readCliPackageJson(baseUrl = import.meta.url) {
7222
7370
  // src/utils/vault-write-gates.ts
7223
7371
  import { execFileSync as execFileSync2 } from "child_process";
7224
7372
  import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
7225
- import { join as join26, relative as relative3 } from "path";
7373
+ import { join as join24, relative as relative3 } from "path";
7226
7374
  var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
7227
7375
  var DEFAULT_CAPTURE_BUDGET = 20;
7228
7376
  var DEFAULT_NO_DECISION_STREAK = 3;
@@ -7321,7 +7469,7 @@ function measureDirtyVolume(vault) {
7321
7469
  if (!rel) continue;
7322
7470
  if (code === "??") {
7323
7471
  untracked += 1;
7324
- const abs = join26(vault, rel);
7472
+ const abs = join24(vault, rel);
7325
7473
  if (existsSync14(abs) && statSync3(abs).isDirectory()) {
7326
7474
  const files = listFilesRecursive(abs);
7327
7475
  expanded += files.length;
@@ -7358,7 +7506,7 @@ function listFilesRecursive(dir) {
7358
7506
  }
7359
7507
  for (const name of entries) {
7360
7508
  if (name === ".git") continue;
7361
- const p = join26(dir, name);
7509
+ const p = join24(dir, name);
7362
7510
  try {
7363
7511
  const st = statSync3(p);
7364
7512
  if (st.isDirectory()) out.push(...listFilesRecursive(p));
@@ -7489,7 +7637,7 @@ function listProjectDayCaptures(vault, project, day) {
7489
7637
  return;
7490
7638
  }
7491
7639
  for (const name of entries) {
7492
- const abs = join26(dir, name);
7640
+ const abs = join24(dir, name);
7493
7641
  const rel = relBase ? `${relBase}/${name}` : name;
7494
7642
  try {
7495
7643
  const st = statSync3(abs);
@@ -7499,16 +7647,16 @@ function listProjectDayCaptures(vault, project, day) {
7499
7647
  }
7500
7648
  }
7501
7649
  };
7502
- walk(join26(vault, "raw", "transcripts"), "raw/transcripts");
7503
- walk(join26(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
7504
- walk(join26(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
7505
- 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");
7506
7654
  if (existsSync14(workRoot)) {
7507
7655
  try {
7508
7656
  for (const name of readdirSync4(workRoot)) {
7509
7657
  if (!name.startsWith(day)) continue;
7510
7658
  if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
7511
- const abs = join26(workRoot, name);
7659
+ const abs = join24(workRoot, name);
7512
7660
  if (statSync3(abs).isDirectory()) {
7513
7661
  for (const f of listFilesRecursive(abs)) {
7514
7662
  const rel = relative3(vault, f).replace(/\\/g, "/");
@@ -7614,8 +7762,8 @@ function runWritePreflight(input) {
7614
7762
  // src/commands/observe.ts
7615
7763
  import { mkdir as mkdir6, writeFile as writeFile4 } from "fs/promises";
7616
7764
  import { existsSync as existsSync15, statSync as statSync4 } from "fs";
7617
- import { join as join27 } from "path";
7618
- import { createHash as createHash6 } from "crypto";
7765
+ import { join as join25 } from "path";
7766
+ import { createHash as createHash7 } from "crypto";
7619
7767
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
7620
7768
  function slugify(text) {
7621
7769
  const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -7663,7 +7811,7 @@ async function runObserve(input) {
7663
7811
  };
7664
7812
  }
7665
7813
  }
7666
- const transcriptsDir = join27(input.vault, "raw", "transcripts");
7814
+ const transcriptsDir = join25(input.vault, "raw", "transcripts");
7667
7815
  try {
7668
7816
  await mkdir6(transcriptsDir, { recursive: true });
7669
7817
  } catch {
@@ -7675,16 +7823,16 @@ async function runObserve(input) {
7675
7823
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7676
7824
  const slug = slugify(input.text);
7677
7825
  const fileName = `${today}-observation-${slug}.md`;
7678
- const filePath = join27(transcriptsDir, fileName);
7826
+ const filePath = join25(transcriptsDir, fileName);
7679
7827
  const body = `
7680
7828
  ${input.text.trim()}
7681
7829
  `;
7682
- const sha256 = createHash6("sha256").update(Buffer.from(body, "utf8")).digest("hex");
7830
+ const sha2562 = createHash7("sha256").update(Buffer.from(body, "utf8")).digest("hex");
7683
7831
  const frontmatterLines = [
7684
7832
  "---",
7685
7833
  "source_url:",
7686
7834
  `ingested: ${today}`,
7687
- `sha256: ${sha256}`,
7835
+ `sha256: ${sha2562}`,
7688
7836
  `kind: ${kind}`
7689
7837
  ];
7690
7838
  if (input.project) {
@@ -7707,17 +7855,17 @@ ${input.text.trim()}
7707
7855
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7708
7856
  });
7709
7857
  const relPath = `raw/transcripts/${fileName}`;
7710
- const humanHint = `created ${relPath} (${sha256.slice(0, 12)}...)`;
7858
+ const humanHint = `created ${relPath} (${sha2562.slice(0, 12)}...)`;
7711
7859
  return {
7712
7860
  exitCode: ExitCode.OK,
7713
- result: ok({ path: relPath, sha256, humanHint })
7861
+ result: ok({ path: relPath, sha256: sha2562, humanHint })
7714
7862
  };
7715
7863
  }
7716
7864
 
7717
7865
  // src/commands/memory.ts
7718
- import { createHash as createHash7 } from "crypto";
7719
- import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile5 } from "fs/promises";
7720
- 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";
7721
7869
 
7722
7870
  // src/utils/memory-authority.ts
7723
7871
  var TIER_RANK = {
@@ -7836,8 +7984,8 @@ async function runMemoryIndex(input) {
7836
7984
  }
7837
7985
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
7838
7986
  const relCachePath = memoryCacheRelPath(input.project);
7839
- const absCachePath = join28(input.vault, relCachePath);
7840
- 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 });
7841
7989
  await writeFile5(absCachePath, `${JSON.stringify({
7842
7990
  generated_at: generatedAt,
7843
7991
  project: input.project,
@@ -8029,7 +8177,7 @@ async function buildMemoryIndexState(pages, project) {
8029
8177
  }
8030
8178
  async function checkMemoryIndex(vault, project, current) {
8031
8179
  const relCachePath = memoryCacheRelPath(project);
8032
- const cacheText = await readIfExists2(join28(vault, relCachePath));
8180
+ const cacheText = await readIfExists2(join26(vault, relCachePath));
8033
8181
  if (!cacheText) {
8034
8182
  return {
8035
8183
  ok: true,
@@ -8146,7 +8294,7 @@ async function readMemoryPage(page, project, warnings) {
8146
8294
  title,
8147
8295
  summary: summarize(body),
8148
8296
  updated,
8149
- hash: createHash7("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
8297
+ hash: createHash8("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
8150
8298
  topics,
8151
8299
  project,
8152
8300
  ...stringField(fm.data.memory_kind) ? { memory_kind: stringField(fm.data.memory_kind) } : {},
@@ -8348,7 +8496,7 @@ function matchPastFailureTarget(target, candidateBody) {
8348
8496
  }
8349
8497
  if (!FAILURE_MARKER_RE.test(candidateBody)) return null;
8350
8498
  const candidateTokens = new Set(tokenizeForMatch(candidate));
8351
- const overlappingTokens = target.distinctiveTokens.filter((token) => candidateTokens.has(token));
8499
+ const overlappingTokens = target.distinctiveTokens.filter((token2) => candidateTokens.has(token2));
8352
8500
  if (overlappingTokens.length >= 2) {
8353
8501
  return { reason: `distinctive target terms overlapped prior failure memory (${overlappingTokens.slice(0, 4).join(", ")})` };
8354
8502
  }
@@ -8364,7 +8512,7 @@ function extractCodeLikeNeedles(value) {
8364
8512
  return (value.match(/[A-Za-z0-9_.:-]*[_.:-][A-Za-z0-9_.:-]+/g) ?? []).filter((needle) => /[A-Za-z]/.test(needle));
8365
8513
  }
8366
8514
  function extractDistinctiveTargetTokens(value) {
8367
- 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)));
8368
8516
  }
8369
8517
  function hasDistinctiveNeedle(value) {
8370
8518
  return extractDistinctiveTargetTokens(value).length > 0;
@@ -8422,23 +8570,23 @@ function renderMemoryIndexStatusHint(status) {
8422
8570
  }
8423
8571
  async function readIfExists2(path) {
8424
8572
  try {
8425
- return await readFile17(path, "utf8");
8573
+ return await readFile18(path, "utf8");
8426
8574
  } catch {
8427
8575
  return "";
8428
8576
  }
8429
8577
  }
8430
8578
  async function collectImportFiles(source) {
8431
- const st = await stat5(source);
8579
+ const st = await stat4(source);
8432
8580
  if (st.isFile()) return isImportCandidate(source) ? [source] : [];
8433
8581
  const files = [];
8434
8582
  await walkImportFiles(source, files);
8435
8583
  return files.sort((a, b) => a.localeCompare(b));
8436
8584
  }
8437
8585
  async function walkImportFiles(dir, out) {
8438
- const entries = await readdir5(dir, { withFileTypes: true });
8586
+ const entries = await readdir4(dir, { withFileTypes: true });
8439
8587
  for (const entry of entries) {
8440
8588
  if (entry.name === ".git" || entry.name === "node_modules") continue;
8441
- const path = join28(dir, entry.name);
8589
+ const path = join26(dir, entry.name);
8442
8590
  if (entry.isDirectory()) {
8443
8591
  await walkImportFiles(path, out);
8444
8592
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -8451,9 +8599,9 @@ function isImportCandidate(path) {
8451
8599
  return ext === ".md" || ext === ".txt";
8452
8600
  }
8453
8601
  async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8454
- const st = await stat5(file);
8602
+ const st = await stat4(file);
8455
8603
  const sourceKind = classifyImportSource(file);
8456
- const hash = createHash7("sha256").update(await readFile17(file)).digest("hex");
8604
+ const hash = createHash8("sha256").update(await readFile18(file)).digest("hex");
8457
8605
  const baseEntry = {
8458
8606
  source_path: file,
8459
8607
  source_kind: sourceKind,
@@ -8476,7 +8624,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
8476
8624
  reason: "policy_source_not_imported"
8477
8625
  };
8478
8626
  }
8479
- const text = await readFile17(file, "utf8");
8627
+ const text = await readFile18(file, "utf8");
8480
8628
  const extracted = extractImportText(text, sourceKind);
8481
8629
  if (!extracted) {
8482
8630
  return {
@@ -8505,8 +8653,8 @@ async function writeImportCapture(vault, entry, today) {
8505
8653
  const content = hiddenString(entry, "__content");
8506
8654
  const project = hiddenString(entry, "__project");
8507
8655
  const relPath = await availableImportPath(vault, entry.proposed_path);
8508
- const absPath = join28(vault, relPath);
8509
- await mkdir7(join28(vault, "raw", "transcripts"), { recursive: true });
8656
+ const absPath = join26(vault, relPath);
8657
+ await mkdir7(join26(vault, "raw", "transcripts"), { recursive: true });
8510
8658
  await writeFile5(absPath, renderImportCapture(entry, content, project, today), "utf8");
8511
8659
  const validation = await runValidate({ file: absPath });
8512
8660
  return {
@@ -8522,7 +8670,7 @@ async function availableImportPath(vault, proposed) {
8522
8670
  const stem = proposed.slice(0, -ext.length);
8523
8671
  let candidate = proposed;
8524
8672
  let i = 2;
8525
- while (await readIfExists2(join28(vault, candidate))) {
8673
+ while (await readIfExists2(join26(vault, candidate))) {
8526
8674
  candidate = `${stem}-${i}${ext}`;
8527
8675
  i++;
8528
8676
  }
@@ -8707,10 +8855,10 @@ function memoryCacheRelPath(project) {
8707
8855
  }
8708
8856
  async function readMemoryCache(vault, project) {
8709
8857
  if (project) {
8710
- const projectCache = await readIfExists2(join28(vault, memoryCacheRelPath(project)));
8858
+ const projectCache = await readIfExists2(join26(vault, memoryCacheRelPath(project)));
8711
8859
  if (projectCache) return projectCache;
8712
8860
  }
8713
- return readIfExists2(join28(vault, ".skillwiki", "memory-topics.json"));
8861
+ return readIfExists2(join26(vault, ".skillwiki", "memory-topics.json"));
8714
8862
  }
8715
8863
  function dedupePages(pages) {
8716
8864
  const seen = /* @__PURE__ */ new Set();
@@ -8801,8 +8949,8 @@ function slugify2(value) {
8801
8949
  }
8802
8950
 
8803
8951
  // src/commands/query.ts
8804
- import { readFile as readFile18, stat as stat6 } from "fs/promises";
8805
- import { join as join29 } from "path";
8952
+ import { readFile as readFile19, stat as stat5 } from "fs/promises";
8953
+ import { join as join27 } from "path";
8806
8954
  var W_KEYWORD = 2;
8807
8955
  var W_SOURCE_OVERLAP = 4;
8808
8956
  var W_WIKILINK = 3;
@@ -8868,8 +9016,22 @@ async function runQuery(input) {
8868
9016
  type: page.type
8869
9017
  };
8870
9018
  }).filter((r) => r.score > 0).sort((a, b) => b.score - a.score || a.path.localeCompare(b.path)).slice(0, limit);
8871
- const humanHint = results.length === 0 ? "no matching pages found" : results.map((r) => `${r.path} (score: ${r.score})`).join("\n");
8872
- 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
+ };
8873
9035
  }
8874
9036
  function scoreSourceOverlap(page, allPages, seedPaths) {
8875
9037
  if (page.sources.length === 0) return 0;
@@ -8923,10 +9085,10 @@ function computeKeywordScore(terms, title, tags, body) {
8923
9085
  return score;
8924
9086
  }
8925
9087
  async function loadOrBuildGraph(vault) {
8926
- const graphPath = join29(vault, ".skillwiki", "graph.json");
9088
+ const graphPath = join27(vault, ".skillwiki", "graph.json");
8927
9089
  let needsBuild = false;
8928
9090
  try {
8929
- const fileStat = await stat6(graphPath);
9091
+ const fileStat = await stat5(graphPath);
8930
9092
  const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
8931
9093
  if (ageHours > 24) needsBuild = true;
8932
9094
  } catch {
@@ -8937,7 +9099,7 @@ async function loadOrBuildGraph(vault) {
8937
9099
  if (buildResult.exitCode !== 0) return null;
8938
9100
  }
8939
9101
  try {
8940
- const raw = await readFile18(graphPath, "utf8");
9102
+ const raw = await readFile19(graphPath, "utf8");
8941
9103
  return JSON.parse(raw);
8942
9104
  } catch {
8943
9105
  return null;
@@ -8952,7 +9114,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8952
9114
  import { z } from "zod";
8953
9115
 
8954
9116
  // src/mcp/vault-resolve.ts
8955
- import { join as join30, resolve as resolve7 } from "path";
9117
+ import { join as join28, resolve as resolve7 } from "path";
8956
9118
 
8957
9119
  // src/mcp/allowlist.ts
8958
9120
  import { resolve as resolve6, sep as sep4 } from "path";
@@ -9014,7 +9176,7 @@ async function resolveMcpVault(input) {
9014
9176
  return ok({ vault: vaultPath, source });
9015
9177
  }
9016
9178
  function defaultGraphOut(vault) {
9017
- return join30(vault, ".skillwiki", "graph.json");
9179
+ return join28(vault, ".skillwiki", "graph.json");
9018
9180
  }
9019
9181
 
9020
9182
  // src/mcp/result-format.ts
@@ -9031,7 +9193,7 @@ function formatToolResult(payload) {
9031
9193
  // src/mcp/audit-log.ts
9032
9194
  import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
9033
9195
  import { homedir } from "os";
9034
- import { join as join31 } from "path";
9196
+ import { join as join29 } from "path";
9035
9197
  function auditEnabled() {
9036
9198
  const v = process.env.SKILLWIKI_MCP_AUDIT;
9037
9199
  if (v === "0" || v === "false") return false;
@@ -9043,7 +9205,7 @@ function auditSink() {
9043
9205
  function auditFilePath() {
9044
9206
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
9045
9207
  if (custom && custom.length > 0) return custom;
9046
- return join31(homedir(), ".skillwiki", "mcp-audit.jsonl");
9208
+ return join29(homedir(), ".skillwiki", "mcp-audit.jsonl");
9047
9209
  }
9048
9210
  function auditMcpToolCall(entry) {
9049
9211
  if (!auditEnabled()) return;
@@ -9053,7 +9215,7 @@ function auditMcpToolCall(entry) {
9053
9215
  return;
9054
9216
  }
9055
9217
  const path = auditFilePath();
9056
- mkdirSync5(join31(path, ".."), { recursive: true });
9218
+ mkdirSync5(join29(path, ".."), { recursive: true });
9057
9219
  appendFileSync(path, line, "utf8");
9058
9220
  }
9059
9221
  async function runMcpToolHandler(tool, input, fn) {
@@ -9273,8 +9435,8 @@ function registerMcpMutatingTools(server) {
9273
9435
  }
9274
9436
 
9275
9437
  // src/mcp/resources.ts
9276
- import { readFile as readFile20 } from "fs/promises";
9277
- import { join as join33 } from "path";
9438
+ import { readFile as readFile21 } from "fs/promises";
9439
+ import { join as join31 } from "path";
9278
9440
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
9279
9441
 
9280
9442
  // src/mcp/lint-bucket.ts
@@ -9402,8 +9564,8 @@ async function fetchQueryPreview(input) {
9402
9564
  }
9403
9565
 
9404
9566
  // src/mcp/graph-html.ts
9405
- import { readFile as readFile19 } from "fs/promises";
9406
- import { join as join32 } from "path";
9567
+ import { readFile as readFile20 } from "fs/promises";
9568
+ import { join as join30 } from "path";
9407
9569
  import { existsSync as existsSync16 } from "fs";
9408
9570
  var TYPE_COLORS = {
9409
9571
  entities: "#e74c3c",
@@ -9472,7 +9634,7 @@ ${nodeSvg}
9472
9634
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
9473
9635
  }
9474
9636
  async function fetchGraphHtmlReport(input) {
9475
- const graphPath = input.graphPath ?? join32(input.vault, ".skillwiki", "graph.json");
9637
+ const graphPath = input.graphPath ?? join30(input.vault, ".skillwiki", "graph.json");
9476
9638
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
9477
9639
  if (!existsSync16(graphPath)) {
9478
9640
  return {
@@ -9482,7 +9644,7 @@ async function fetchGraphHtmlReport(input) {
9482
9644
  }
9483
9645
  let raw;
9484
9646
  try {
9485
- raw = await readFile19(graphPath, "utf8");
9647
+ raw = await readFile20(graphPath, "utf8");
9486
9648
  } catch (e) {
9487
9649
  return {
9488
9650
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -9546,7 +9708,7 @@ async function fetchStaleSummary(input) {
9546
9708
 
9547
9709
  // src/mcp/resources.ts
9548
9710
  async function readVaultFile(vault, rel) {
9549
- return readFile20(join33(vault, rel), "utf8");
9711
+ return readFile21(join31(vault, rel), "utf8");
9550
9712
  }
9551
9713
  async function tailLines(text, lines) {
9552
9714
  const parts = text.split(/\r?\n/);
@@ -9632,9 +9794,9 @@ function registerMcpResources(server) {
9632
9794
  if (!v.ok) {
9633
9795
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
9634
9796
  }
9635
- const path = join33(v.data.vault, ".skillwiki", "graph.json");
9797
+ const path = join31(v.data.vault, ".skillwiki", "graph.json");
9636
9798
  try {
9637
- const raw = await readFile20(path, "utf8");
9799
+ const raw = await readFile21(path, "utf8");
9638
9800
  const graph = JSON.parse(raw);
9639
9801
  const adjacency = graph.adjacency ?? {};
9640
9802
  const nodes = Object.keys(adjacency);
@@ -9947,11 +10109,6 @@ export {
9947
10109
  readLastOp,
9948
10110
  appendLastOp,
9949
10111
  clearLastOp,
9950
- eventPathFor,
9951
- validateLogEvent,
9952
- canonicalEventJson,
9953
- writeLogEvent,
9954
- readLogEvents,
9955
10112
  runLogAppend,
9956
10113
  renderIndexUpsert,
9957
10114
  upsertIndexEntry,
@@ -9967,7 +10124,6 @@ export {
9967
10124
  resolveInitTimePath,
9968
10125
  resolveRuntimePath,
9969
10126
  runOrphans,
9970
- extractCitationMarkers,
9971
10127
  runAudit,
9972
10128
  findPlugin,
9973
10129
  extractTaxonomy,
@@ -9979,6 +10135,8 @@ export {
9979
10135
  runIndexCheck,
9980
10136
  renderProjectIndex,
9981
10137
  runProjectIndex,
10138
+ planRawStructuralMove,
10139
+ applyRawStructuralMove,
9982
10140
  runStale,
9983
10141
  runPagesize,
9984
10142
  runLogRotate,
@@ -9989,8 +10147,10 @@ export {
9989
10147
  buildRemoteObjectPath,
9990
10148
  isValidRemoteDeleteCap,
9991
10149
  planAndMaybePruneRemoteObjects,
9992
- runDedup,
9993
10150
  safeWritePage,
10151
+ rewriteRawSourceReferences,
10152
+ snapshotMaintainedPageState,
10153
+ runDedup,
9994
10154
  runFrontmatterFix,
9995
10155
  fixPathTooLong,
9996
10156
  assessSourceIdentity,