skillwiki 0.10.47 → 0.10.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  scanConflictMarkerBlocksInText,
41
41
  snapshotterHealthChecks,
42
42
  upsertIndexEntry
43
- } from "./chunk-5JYOYMST.js";
43
+ } from "./chunk-F7LXKP4T.js";
44
44
  import {
45
45
  normalizeDistTag,
46
46
  readCache,
@@ -56,6 +56,7 @@ import {
56
56
  } from "./chunk-GAU4D25O.js";
57
57
  import {
58
58
  FLEET_REL_PATH,
59
+ REDACTED_MALFORMED_REFERENCE,
59
60
  VAULT_COMMIT_PATHSPEC,
60
61
  acquireOwnedSyncLock,
61
62
  appendLastOp,
@@ -79,7 +80,9 @@ import {
79
80
  loadFleetManifestAndHost,
80
81
  markJournalSuperseded,
81
82
  mergeTaxonomyConflict,
83
+ normalizeProjectSlug,
82
84
  normalizeRemoteRoot,
85
+ parseActiveWorkPath,
83
86
  parseDotenvFile,
84
87
  parseDotenvText,
85
88
  parseJournalEnv,
@@ -119,6 +122,7 @@ import {
119
122
  runSyncUnlock,
120
123
  runTagAudit,
121
124
  runTopicMapCheck,
125
+ safeDiagnosticValue,
122
126
  safeWritePage,
123
127
  serializeJournalEnv,
124
128
  snapshotMaintainedPageState,
@@ -127,7 +131,7 @@ import {
127
131
  supersedeStaleReviewRequiredJournals,
128
132
  taxonomyCommentForPage,
129
133
  writeDotenv
130
- } from "./chunk-4SINVFR3.js";
134
+ } from "./chunk-FXASPB3C.js";
131
135
  import {
132
136
  assertTargetInsideVault,
133
137
  atomicWriteText,
@@ -166,17 +170,20 @@ import {
166
170
  err,
167
171
  extractFrontmatter,
168
172
  isBlockedHost,
173
+ mapWithConcurrency,
169
174
  ok,
170
175
  readPage,
176
+ readPageCached,
171
177
  redactSensitiveContent,
172
178
  resolveReadOnlyVaultRoot,
173
179
  scanSensitiveContent,
174
180
  scanVault,
175
- splitFrontmatter
181
+ splitFrontmatter,
182
+ vaultIoConcurrency
176
183
  } from "./chunk-EVQASYII.js";
177
184
 
178
185
  // src/cli.ts
179
- import { join as join38 } from "path";
186
+ import { join as join39 } from "path";
180
187
  import { Command } from "commander";
181
188
 
182
189
  // src/utils/output.ts
@@ -2016,9 +2023,6 @@ function extractDate(filename) {
2016
2023
  function extractSlugFromFilename(filename) {
2017
2024
  return filename.replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/^(task|bug|idea|note|observation)-/, "").replace(/\.md$/, "");
2018
2025
  }
2019
- function extractProjectSlug(projectField) {
2020
- return projectField.replace(/^\[\[/, "").replace(/\]\]$/, "").replace(/^"|"$/g, "");
2021
- }
2022
2026
  async function runClaim(input) {
2023
2027
  if (!existsSync2(input.vault) || !statSync(input.vault).isDirectory()) {
2024
2028
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { path: input.vault }) };
@@ -2029,10 +2033,19 @@ async function runClaim(input) {
2029
2033
  }
2030
2034
  const content = await readFile5(absTranscript, "utf8");
2031
2035
  const fm = extractFrontmatter(content);
2032
- let projectSlug = input.project;
2033
- if (!projectSlug && fm.ok && typeof fm.data.project === "string") {
2034
- projectSlug = extractProjectSlug(fm.data.project);
2036
+ const explicitProject = fm.ok ? normalizeProjectSlug(fm.data.project) : void 0;
2037
+ if (input.project && explicitProject && explicitProject !== input.project) {
2038
+ return {
2039
+ exitCode: ExitCode.SCHEME_REJECTED,
2040
+ result: err("PROJECT_MISMATCH", {
2041
+ message: `--project ${input.project} does not match the capture's explicit project ${explicitProject}`,
2042
+ project: input.project,
2043
+ captureProject: explicitProject,
2044
+ transcript: input.transcript
2045
+ })
2046
+ };
2035
2047
  }
2048
+ const projectSlug = explicitProject ?? input.project;
2036
2049
  if (!projectSlug) {
2037
2050
  return {
2038
2051
  exitCode: ExitCode.SCHEME_REJECTED,
@@ -3525,10 +3538,10 @@ async function runRemove(input) {
3525
3538
  const tombstonePath = await writeDeleteIntent(input.vault, intent);
3526
3539
  await unlink2(join19(input.vault, relPath));
3527
3540
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
3528
- const { readFile: readFile20 } = await import("fs/promises");
3541
+ const { readFile: readFile21 } = await import("fs/promises");
3529
3542
  const { join: pathJoin } = await import("path");
3530
3543
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-BJNZUPH6.js");
3531
- const before = await readFile20(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
3544
+ const before = await readFile21(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
3532
3545
  const fullTarget = relPath.replace(/\.md$/, "");
3533
3546
  const bare = fullTarget.split("/").pop() ?? fullTarget;
3534
3547
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -4161,11 +4174,148 @@ async function runTranscripts(input) {
4161
4174
  return { exitCode: ExitCode.OK, result: ok({ transcripts, humanHint: hint }) };
4162
4175
  }
4163
4176
 
4177
+ // src/commands/claims-audit.ts
4178
+ import { readFile as readFile10 } from "fs/promises";
4179
+ import { join as join23 } from "path";
4180
+ function safeProjectPresentation(raw) {
4181
+ if (safeDiagnosticValue(raw) === REDACTED_MALFORMED_REFERENCE) {
4182
+ return REDACTED_MALFORMED_REFERENCE;
4183
+ }
4184
+ return normalizeProjectSlug(raw) ?? REDACTED_MALFORMED_REFERENCE;
4185
+ }
4186
+ async function runClaimsAudit(input) {
4187
+ const scanResult = await scanVault(input.vault);
4188
+ if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
4189
+ const activeTranscripts = scanResult.data.raw.filter((p) => p.relPath.startsWith("raw/transcripts/") && p.relPath.endsWith(".md")).sort((a, b) => a.relPath.localeCompare(b.relPath));
4190
+ const activePathSet = new Set(activeTranscripts.map((p) => p.relPath));
4191
+ const workItemDirs = /* @__PURE__ */ new Set();
4192
+ const specDirs = /* @__PURE__ */ new Set();
4193
+ for (const page of scanResult.data.workItems) {
4194
+ const slash = page.relPath.lastIndexOf("/");
4195
+ if (slash < 0) continue;
4196
+ const dir = page.relPath.slice(0, slash);
4197
+ if (!parseActiveWorkPath(dir)) continue;
4198
+ workItemDirs.add(dir);
4199
+ if (page.relPath.endsWith("/spec.md")) specDirs.add(dir);
4200
+ }
4201
+ const sortedDirs = [...workItemDirs].sort();
4202
+ const slugByDir = new Map(sortedDirs.map((dir) => [dir, parseActiveWorkPath(dir)?.project]));
4203
+ if (input.project) {
4204
+ const knownSlugs = new Set(
4205
+ [...slugByDir.values()].filter((slug) => Boolean(slug))
4206
+ );
4207
+ if (!knownSlugs.has(input.project)) {
4208
+ return {
4209
+ exitCode: ExitCode.PROJECT_NOT_FOUND,
4210
+ result: err("UNKNOWN_PROJECT", `Project "${input.project}" not found. Available: ${[...knownSlugs].sort().join(", ") || "(none)"}`)
4211
+ };
4212
+ }
4213
+ }
4214
+ const scopedSpecDirs = sortedDirs.filter((relDir) => {
4215
+ if (!specDirs.has(relDir)) return false;
4216
+ return !input.project || slugByDir.get(relDir) === input.project;
4217
+ });
4218
+ const claimSourceResults = await mapWithConcurrency(scopedSpecDirs, vaultIoConcurrency(), async (relDir) => {
4219
+ const specPath = join23(input.vault, relDir, "spec.md");
4220
+ try {
4221
+ const specText = await readFile10(specPath, "utf8");
4222
+ const fm = extractFrontmatter(specText);
4223
+ if (!fm.ok) return null;
4224
+ return {
4225
+ relDir,
4226
+ source: fm.data.source,
4227
+ sources: fm.data.sources,
4228
+ closes: fm.data.closes
4229
+ };
4230
+ } catch {
4231
+ return null;
4232
+ }
4233
+ });
4234
+ const claimIndex = collectClaimedTranscripts(
4235
+ claimSourceResults.filter((source) => source !== null)
4236
+ );
4237
+ const claimedByPath = claimIndex.claimedByPath;
4238
+ const transcriptMeta = /* @__PURE__ */ new Map();
4239
+ const transcriptEntries = await mapWithConcurrency(activeTranscripts, vaultIoConcurrency(), async (t) => {
4240
+ try {
4241
+ const text = await readPageCached(t);
4242
+ const fm = extractFrontmatter(text);
4243
+ if (!fm.ok) return null;
4244
+ const projectRaw = typeof fm.data.project === "string" ? fm.data.project.trim() : void 0;
4245
+ const workItemRaw = typeof fm.data.work_item === "string" ? fm.data.work_item.trim() : void 0;
4246
+ const meta = {};
4247
+ if (projectRaw) {
4248
+ meta.projectRaw = projectRaw;
4249
+ meta.project = normalizeProjectSlug(projectRaw);
4250
+ }
4251
+ if (workItemRaw) meta.workItem = workItemRaw;
4252
+ return [t.relPath, meta];
4253
+ } catch {
4254
+ return null;
4255
+ }
4256
+ });
4257
+ for (const entry of transcriptEntries) {
4258
+ if (entry) transcriptMeta.set(entry[0], entry[1]);
4259
+ }
4260
+ const findings = [];
4261
+ for (const d of claimIndex.diagnostics) {
4262
+ if (d.kind === "duplicate") {
4263
+ findings.push({ kind: "duplicate_claim", path: d.path, owners: d.owners });
4264
+ } else {
4265
+ findings.push({ kind: "malformed_claim_reference", relDir: d.relDir, field: d.field, value: d.value });
4266
+ }
4267
+ }
4268
+ for (const [path, owner] of claimedByPath) {
4269
+ if (!activePathSet.has(path)) {
4270
+ findings.push({ kind: "dangling_claim_reference", path, claimedBy: owner });
4271
+ }
4272
+ }
4273
+ for (const [path, owner] of claimedByPath) {
4274
+ const meta = transcriptMeta.get(path);
4275
+ const ownerProject = slugByDir.get(owner) ?? parseActiveWorkPath(owner)?.project;
4276
+ if (meta?.project && ownerProject !== meta.project) {
4277
+ findings.push({
4278
+ kind: "project_mismatch",
4279
+ path,
4280
+ captureProject: safeProjectPresentation(meta.projectRaw ?? ""),
4281
+ claimedByProject: ownerProject ?? "",
4282
+ claimedBy: owner
4283
+ });
4284
+ }
4285
+ }
4286
+ for (const t of activeTranscripts) {
4287
+ const meta = transcriptMeta.get(t.relPath);
4288
+ if (!meta?.workItem) continue;
4289
+ if (input.project && meta.project && meta.project !== input.project) continue;
4290
+ if (claimedByPath.has(t.relPath)) continue;
4291
+ findings.push({ kind: "work_item_unbacked_claim", path: t.relPath, workItem: safeDiagnosticValue(meta.workItem) });
4292
+ }
4293
+ const summary = {
4294
+ duplicate_claim: 0,
4295
+ malformed_claim_reference: 0,
4296
+ dangling_claim_reference: 0,
4297
+ project_mismatch: 0,
4298
+ work_item_unbacked_claim: 0
4299
+ };
4300
+ const hintLines = [];
4301
+ for (const f of findings) {
4302
+ summary[f.kind] += 1;
4303
+ if (f.kind === "duplicate_claim") hintLines.push(`duplicate_claim: ${f.path} \u2014 ${f.owners.join(", ")}`);
4304
+ else if (f.kind === "malformed_claim_reference") hintLines.push(`malformed_claim_reference: ${f.relDir} ${f.field}=${f.value}`);
4305
+ else if (f.kind === "dangling_claim_reference") hintLines.push(`dangling_claim_reference: ${f.path} \u2014 claimed by ${f.claimedBy}`);
4306
+ else if (f.kind === "project_mismatch") hintLines.push(`project_mismatch: ${f.path} \u2014 ${f.captureProject} claimed by ${f.claimedByProject}`);
4307
+ else hintLines.push(`work_item_unbacked_claim: ${f.path} \u2014 ${f.workItem}`);
4308
+ }
4309
+ if (hintLines.length === 0) hintLines.push("no claim-integrity findings");
4310
+ const humanHint = hintLines.join("\n");
4311
+ return { exitCode: ExitCode.OK, result: ok({ findings, summary, humanHint }) };
4312
+ }
4313
+
4164
4314
  // src/commands/compound.ts
4165
4315
  import { writeFile as writeFile6, mkdir as mkdir7, readdir as readdir6, unlink as unlink3 } from "fs/promises";
4166
- import { join as join23 } from "path";
4316
+ import { join as join24 } from "path";
4167
4317
  import { existsSync as existsSync7 } from "fs";
4168
- import { readFile as readFile10 } from "fs/promises";
4318
+ import { readFile as readFile11 } from "fs/promises";
4169
4319
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
4170
4320
  var FIELD_RE = {
4171
4321
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -4263,17 +4413,17 @@ function extractRetroFields(date, cycleName, block) {
4263
4413
  };
4264
4414
  }
4265
4415
  async function runCompound(input) {
4266
- const logPath = join23(input.vault, "log.md");
4416
+ const logPath = join24(input.vault, "log.md");
4267
4417
  let logText;
4268
4418
  try {
4269
- logText = await readFile10(logPath, "utf8");
4419
+ logText = await readFile11(logPath, "utf8");
4270
4420
  } catch {
4271
4421
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
4272
4422
  }
4273
4423
  const entries = parseRetroEntries(logText);
4274
4424
  const promoted = [];
4275
4425
  const skipped = [];
4276
- const compoundDir = join23(input.vault, "projects", input.project, "compound");
4426
+ const compoundDir = join24(input.vault, "projects", input.project, "compound");
4277
4427
  for (const entry of entries) {
4278
4428
  const generalizeValue = entry.generalize.trim();
4279
4429
  if (!/^yes/i.test(generalizeValue)) {
@@ -4281,7 +4431,7 @@ async function runCompound(input) {
4281
4431
  continue;
4282
4432
  }
4283
4433
  const slug = slugify(entry.cycleName);
4284
- const compoundPath = join23(compoundDir, `${slug}.md`);
4434
+ const compoundPath = join24(compoundDir, `${slug}.md`);
4285
4435
  if (existsSync7(compoundPath)) {
4286
4436
  skipped.push(entry.date);
4287
4437
  continue;
@@ -4342,7 +4492,7 @@ async function runCompound(input) {
4342
4492
  };
4343
4493
  }
4344
4494
  async function runCompoundDelete(input) {
4345
- const projectDir = join23(input.vault, "projects", input.project);
4495
+ const projectDir = join24(input.vault, "projects", input.project);
4346
4496
  if (!existsSync7(projectDir)) {
4347
4497
  return {
4348
4498
  exitCode: ExitCode.PROJECT_NOT_FOUND,
@@ -4350,7 +4500,7 @@ async function runCompoundDelete(input) {
4350
4500
  };
4351
4501
  }
4352
4502
  const entryName = input.entry.replace(/\.md$/, "");
4353
- const compoundPath = join23(projectDir, "compound", `${entryName}.md`);
4503
+ const compoundPath = join24(projectDir, "compound", `${entryName}.md`);
4354
4504
  if (!existsSync7(compoundPath)) {
4355
4505
  return {
4356
4506
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -4384,7 +4534,7 @@ knowledge.md regenerated`
4384
4534
  };
4385
4535
  }
4386
4536
  async function runCompoundList(input) {
4387
- const compoundDir = join23(input.vault, "projects", input.project, "compound");
4537
+ const compoundDir = join24(input.vault, "projects", input.project, "compound");
4388
4538
  if (!existsSync7(compoundDir)) {
4389
4539
  return {
4390
4540
  exitCode: ExitCode.OK,
@@ -4415,10 +4565,10 @@ could not read compound directory`
4415
4565
  const entries = [];
4416
4566
  for (const dirent of dirents) {
4417
4567
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
4418
- const filePath = join23(compoundDir, dirent.name);
4568
+ const filePath = join24(compoundDir, dirent.name);
4419
4569
  let text;
4420
4570
  try {
4421
- text = await readFile10(filePath, "utf8");
4571
+ text = await readFile11(filePath, "utf8");
4422
4572
  } catch {
4423
4573
  continue;
4424
4574
  }
@@ -4447,8 +4597,8 @@ no compound entries found`;
4447
4597
  }
4448
4598
 
4449
4599
  // src/commands/session-brief.ts
4450
- import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile7 } from "fs/promises";
4451
- import { join as join24, relative, sep } from "path";
4600
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile7 } from "fs/promises";
4601
+ import { join as join25, relative, sep } from "path";
4452
4602
  var MAX_WORDS = 900;
4453
4603
  async function runSessionBrief(input) {
4454
4604
  const { root: scanRoot } = resolveReadOnlyVaultRoot(input.vault);
@@ -4557,7 +4707,7 @@ async function resolveProject(input) {
4557
4707
  const envProject = input.env?.SKILLWIKI_PROJECT;
4558
4708
  if (envProject) return envProject;
4559
4709
  const cwd = input.cwd ?? process.cwd();
4560
- const projectDotenv = await readProjectSlug(join24(cwd, ".skillwiki", ".env"));
4710
+ const projectDotenv = await readProjectSlug(join25(cwd, ".skillwiki", ".env"));
4561
4711
  if (projectDotenv) return projectDotenv;
4562
4712
  const inferred = inferProjectFromPath(input.vault, cwd);
4563
4713
  if (inferred) return inferred;
@@ -4566,7 +4716,7 @@ async function resolveProject(input) {
4566
4716
  async function readProjectSlug(file) {
4567
4717
  let text;
4568
4718
  try {
4569
- text = await readFile11(file, "utf8");
4719
+ text = await readFile12(file, "utf8");
4570
4720
  } catch {
4571
4721
  return void 0;
4572
4722
  }
@@ -4657,7 +4807,7 @@ async function loadTrendDigests(typedPages) {
4657
4807
  return out;
4658
4808
  }
4659
4809
  async function loadSessionPins(vault, project) {
4660
- const text = await readIfExists(join24(vault, "meta", "session-pins.md"));
4810
+ const text = await readIfExists(join25(vault, "meta", "session-pins.md"));
4661
4811
  if (!text) return [];
4662
4812
  const fm = extractFrontmatter(text);
4663
4813
  if (!fm.ok) return [];
@@ -4776,7 +4926,7 @@ function satelliteHealthWarnings(warning) {
4776
4926
  return warning ? [warning] : [];
4777
4927
  }
4778
4928
  async function loadHealthWarnings(vault) {
4779
- const text = await readIfExists(join24(vault, ".skillwiki", "health.json"));
4929
+ const text = await readIfExists(join25(vault, ".skillwiki", "health.json"));
4780
4930
  if (!text) return [];
4781
4931
  try {
4782
4932
  const parsed = JSON.parse(text);
@@ -4789,7 +4939,7 @@ async function loadHealthWarnings(vault) {
4789
4939
  }
4790
4940
  }
4791
4941
  async function loadMemoryTopics(vault, project) {
4792
- const text = await readIfExists(join24(vault, ".skillwiki", "memory", project, "topics.json"));
4942
+ const text = await readIfExists(join25(vault, ".skillwiki", "memory", project, "topics.json"));
4793
4943
  if (!text) return [];
4794
4944
  try {
4795
4945
  const parsed = JSON.parse(text);
@@ -4814,11 +4964,11 @@ async function loadMemoryTopics(vault, project) {
4814
4964
  }
4815
4965
  }
4816
4966
  async function writeBriefArtifacts(vault, input) {
4817
- const metaPath = join24(vault, "meta", "latest-session-brief.md");
4818
- const cacheMdPath = join24(vault, ".skillwiki", "session-brief.md");
4819
- const cacheJsonPath = join24(vault, ".skillwiki", "session-brief.json");
4820
- await mkdir8(join24(vault, "meta"), { recursive: true });
4821
- await mkdir8(join24(vault, ".skillwiki"), { recursive: true });
4967
+ const metaPath = join25(vault, "meta", "latest-session-brief.md");
4968
+ const cacheMdPath = join25(vault, ".skillwiki", "session-brief.md");
4969
+ const cacheJsonPath = join25(vault, ".skillwiki", "session-brief.json");
4970
+ await mkdir8(join25(vault, "meta"), { recursive: true });
4971
+ await mkdir8(join25(vault, ".skillwiki"), { recursive: true });
4822
4972
  const committed = renderCommittedBrief(input);
4823
4973
  const previousComparable = comparableBrief(await readIfExists(metaPath));
4824
4974
  const nextComparable = comparableBrief(committed);
@@ -4888,7 +5038,7 @@ function renderCommittedBrief(input) {
4888
5038
  ].filter((line) => line !== "").join("\n");
4889
5039
  }
4890
5040
  async function rebuildRootIndexProjection(vault) {
4891
- const before = await readIfExists(join24(vault, "index.md"));
5041
+ const before = await readIfExists(join25(vault, "index.md"));
4892
5042
  if (!before) return false;
4893
5043
  const projection = await renderRootIndex({ vault, currentText: before });
4894
5044
  if (!projection.ok) return false;
@@ -4898,7 +5048,7 @@ async function rebuildRootIndexProjection(vault) {
4898
5048
  }
4899
5049
  async function readIfExists(path) {
4900
5050
  try {
4901
- return await readFile11(path, "utf8");
5051
+ return await readFile12(path, "utf8");
4902
5052
  } catch {
4903
5053
  return "";
4904
5054
  }
@@ -4940,14 +5090,14 @@ function dateFromPath(path) {
4940
5090
  }
4941
5091
 
4942
5092
  // src/commands/ingest.ts
4943
- import { readFile as readFile13, open, unlink as unlink4, mkdir as mkdir10 } from "fs/promises";
4944
- import { join as join26 } from "path";
5093
+ import { readFile as readFile14, open, unlink as unlink4, mkdir as mkdir10 } from "fs/promises";
5094
+ import { join as join27 } from "path";
4945
5095
  import { createHash as createHash4 } from "crypto";
4946
5096
 
4947
5097
  // src/commands/page-publish.ts
4948
5098
  import { readFileSync as readFileSync12, realpathSync } from "fs";
4949
- import { mkdir as mkdir9, readFile as readFile12 } from "fs/promises";
4950
- import { dirname as dirname6, join as join25, resolve as resolve3 } from "path";
5099
+ import { mkdir as mkdir9, readFile as readFile13 } from "fs/promises";
5100
+ import { dirname as dirname6, join as join26, resolve as resolve3 } from "path";
4951
5101
 
4952
5102
  // src/utils/publication-approval.ts
4953
5103
  import { createHash as createHash3 } from "crypto";
@@ -5240,7 +5390,7 @@ async function refreshProjectIndexForTarget(vault, projectSlugs, today) {
5240
5390
  for (const slug of projectSlugs) {
5241
5391
  const rendered = await renderProjectIndex(vault, slug, { today });
5242
5392
  if (!rendered.ok) return rendered;
5243
- const indexPath = join25(vault, rendered.data.index_path);
5393
+ const indexPath = join26(vault, rendered.data.index_path);
5244
5394
  try {
5245
5395
  await mkdir9(dirname6(indexPath), { recursive: true });
5246
5396
  } catch (error) {
@@ -5249,7 +5399,7 @@ async function refreshProjectIndexForTarget(vault, projectSlugs, today) {
5249
5399
  const written = await atomicWriteText(indexPath, rendered.data.text);
5250
5400
  if (!written.ok) return written;
5251
5401
  try {
5252
- const visible = await readFile12(indexPath, "utf8");
5402
+ const visible = await readFile13(indexPath, "utf8");
5253
5403
  if (visible !== rendered.data.text) {
5254
5404
  return err("WRITE_FAILED", { path: indexPath, message: "project index verification failed" });
5255
5405
  }
@@ -5325,7 +5475,7 @@ function preparePagePublicationFromContent(input) {
5325
5475
  async function preparePagePublication(input) {
5326
5476
  let content;
5327
5477
  try {
5328
- content = await readFile12(input.draftPath, "utf8");
5478
+ content = await readFile13(input.draftPath, "utf8");
5329
5479
  } catch (error) {
5330
5480
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
5331
5481
  }
@@ -5397,10 +5547,10 @@ async function runLockedPrimaryStages(input, vault, deps) {
5397
5547
  ExitCode.VAULT_PATH_INVALID
5398
5548
  );
5399
5549
  }
5400
- const schemaPath = join25(vault, "SCHEMA.md");
5550
+ const schemaPath = join26(vault, "SCHEMA.md");
5401
5551
  let schemaText;
5402
5552
  try {
5403
- schemaText = await readFile12(schemaPath, "utf8");
5553
+ schemaText = await readFile13(schemaPath, "utf8");
5404
5554
  } catch (error) {
5405
5555
  return lockedFailure("schema", state, err("WRITE_FAILED", { message: String(error) }));
5406
5556
  }
@@ -5430,8 +5580,8 @@ async function runLockedPrimaryStages(input, vault, deps) {
5430
5580
  let visibleSchema;
5431
5581
  try {
5432
5582
  [visible, visibleSchema] = await Promise.all([
5433
- readFile12(input.targetPath, "utf8"),
5434
- readFile12(schemaPath, "utf8")
5583
+ readFile13(input.targetPath, "utf8"),
5584
+ readFile13(schemaPath, "utf8")
5435
5585
  ]);
5436
5586
  } catch (error) {
5437
5587
  return lockedFailure("verify", state, err("WRITE_FAILED", { message: String(error) }));
@@ -5535,17 +5685,17 @@ function renderPublicationLog(input, added) {
5535
5685
  }
5536
5686
  async function readPageChanged(targetPath, content) {
5537
5687
  try {
5538
- return ok(await readFile12(targetPath, "utf8") !== content);
5688
+ return ok(await readFile13(targetPath, "utf8") !== content);
5539
5689
  } catch (error) {
5540
5690
  if (error.code === "ENOENT") return ok(true);
5541
5691
  return err("WRITE_FAILED", { path: targetPath, message: String(error) });
5542
5692
  }
5543
5693
  }
5544
5694
  async function previewPreparedPagePublication(input, vault) {
5545
- const schemaPath = join25(vault, "SCHEMA.md");
5695
+ const schemaPath = join26(vault, "SCHEMA.md");
5546
5696
  let schemaText;
5547
5697
  try {
5548
- schemaText = await readFile12(schemaPath, "utf8");
5698
+ schemaText = await readFile13(schemaPath, "utf8");
5549
5699
  } catch (error) {
5550
5700
  const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
5551
5701
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -5557,10 +5707,10 @@ async function previewPreparedPagePublication(input, vault) {
5557
5707
  if (!reconciled.ok) return { exitCode: errorExitCode(reconciled.error), result: reconciled };
5558
5708
  const pageChanged = await readPageChanged(input.targetPath, input.page.content);
5559
5709
  if (!pageChanged.ok) return { exitCode: errorExitCode(pageChanged.error), result: pageChanged };
5560
- const indexPath = join25(vault, "index.md");
5710
+ const indexPath = join26(vault, "index.md");
5561
5711
  let indexText;
5562
5712
  try {
5563
- indexText = await readFile12(indexPath, "utf8");
5713
+ indexText = await readFile13(indexPath, "utf8");
5564
5714
  } catch (error) {
5565
5715
  const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
5566
5716
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -5576,7 +5726,7 @@ async function previewPreparedPagePublication(input, vault) {
5576
5726
  for (const projectSlug of projectSlugsForPublication(input.page.target, input.page.content)) {
5577
5727
  const renderedProject = await renderProjectIndex(vault, projectSlug, { today: input.date });
5578
5728
  if (!renderedProject.ok) return { exitCode: errorExitCode(renderedProject.error), result: renderedProject };
5579
- const projectIndexPath = join25(vault, renderedProject.data.index_path);
5729
+ const projectIndexPath = join26(vault, renderedProject.data.index_path);
5580
5730
  const projectIndexChanged = await readPageChanged(projectIndexPath, renderedProject.data.text);
5581
5731
  if (!projectIndexChanged.ok) {
5582
5732
  return { exitCode: errorExitCode(projectIndexChanged.error), result: projectIndexChanged };
@@ -5586,10 +5736,10 @@ async function previewPreparedPagePublication(input, vault) {
5586
5736
  projectIndexPaths.push(renderedProject.data.index_path);
5587
5737
  }
5588
5738
  }
5589
- const logPath = join25(vault, "log.md");
5739
+ const logPath = join26(vault, "log.md");
5590
5740
  let logText;
5591
5741
  try {
5592
- logText = await readFile12(logPath, "utf8");
5742
+ logText = await readFile13(logPath, "utf8");
5593
5743
  } catch (error) {
5594
5744
  const result = err("FILE_NOT_FOUND", { path: logPath, message: String(error) });
5595
5745
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -5883,7 +6033,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
5883
6033
  }
5884
6034
  async function resolveRawCapture(input) {
5885
6035
  try {
5886
- const existing = await readFile13(input.path, "utf8");
6036
+ const existing = await readFile14(input.path, "utf8");
5887
6037
  const frontmatter = extractFrontmatter(existing);
5888
6038
  if (!frontmatter.ok) {
5889
6039
  return err("INGEST_VALIDATION_FAILED", {
@@ -6022,7 +6172,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
6022
6172
  sourceContent = fetchResult.data.body;
6023
6173
  } else {
6024
6174
  try {
6025
- sourceContent = await readFile13(input.source, "utf8");
6175
+ sourceContent = await readFile14(input.source, "utf8");
6026
6176
  } catch {
6027
6177
  return {
6028
6178
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6047,7 +6197,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
6047
6197
  const rawRelPath = `raw/articles/${slug}.md`;
6048
6198
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
6049
6199
  const typedRelPath = `${typedDir}/${slug}.md`;
6050
- const rawAbsPath = join26(input.vault, rawRelPath);
6200
+ const rawAbsPath = join27(input.vault, rawRelPath);
6051
6201
  const identity = assessSourceIdentity({
6052
6202
  rawPath: rawRelPath,
6053
6203
  sourceUrl: sourceUrl ?? void 0,
@@ -6091,11 +6241,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
6091
6241
  );
6092
6242
  if (!input.dryRun) {
6093
6243
  try {
6094
- await mkdir10(join26(input.vault, typedDir), { recursive: true });
6244
+ await mkdir10(join27(input.vault, typedDir), { recursive: true });
6095
6245
  } catch (error) {
6096
6246
  return {
6097
6247
  exitCode: ExitCode.WRITE_FAILED,
6098
- result: err("WRITE_FAILED", { path: join26(input.vault, typedDir), message: String(error) })
6248
+ result: err("WRITE_FAILED", { path: join27(input.vault, typedDir), message: String(error) })
6099
6249
  };
6100
6250
  }
6101
6251
  }
@@ -6149,11 +6299,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
6149
6299
  preflight: pagePublishDeps.preflight,
6150
6300
  mutate: async (receipt) => {
6151
6301
  try {
6152
- await mkdir10(join26(input.vault, "raw", "articles"), { recursive: true });
6302
+ await mkdir10(join27(input.vault, "raw", "articles"), { recursive: true });
6153
6303
  } catch (error) {
6154
6304
  return {
6155
6305
  exitCode: ExitCode.WRITE_FAILED,
6156
- result: err("WRITE_FAILED", { path: join26(input.vault, "raw", "articles"), message: String(error) })
6306
+ result: err("WRITE_FAILED", { path: join27(input.vault, "raw", "articles"), message: String(error) })
6157
6307
  };
6158
6308
  }
6159
6309
  const rawWrite = await writeResolvedRaw({
@@ -6389,8 +6539,8 @@ ${body}`;
6389
6539
  }
6390
6540
 
6391
6541
  // src/commands/tag-reconcile.ts
6392
- import { readFile as readFile14 } from "fs/promises";
6393
- import { join as join27, posix } from "path";
6542
+ import { readFile as readFile15 } from "fs/promises";
6543
+ import { join as join28, posix } from "path";
6394
6544
  var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
6395
6545
  function errorExitCode2(error) {
6396
6546
  switch (error) {
@@ -6433,7 +6583,7 @@ function asTagArray(frontmatter, path) {
6433
6583
  async function readTagsFromFile(path) {
6434
6584
  let text;
6435
6585
  try {
6436
- text = await readFile14(path, "utf8");
6586
+ text = await readFile15(path, "utf8");
6437
6587
  } catch (error) {
6438
6588
  if (error.code === "ENOENT") {
6439
6589
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
@@ -6454,7 +6604,7 @@ async function resolveRequestedTags(input, page) {
6454
6604
  result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
6455
6605
  };
6456
6606
  }
6457
- const source = input.from ?? (explicit.length === 0 ? join27(input.vault, page) : void 0);
6607
+ const source = input.from ?? (explicit.length === 0 ? join28(input.vault, page) : void 0);
6458
6608
  if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
6459
6609
  const sourced = await readTagsFromFile(source);
6460
6610
  if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
@@ -6465,7 +6615,7 @@ async function resolveRequestedTags(input, page) {
6465
6615
  }
6466
6616
  async function readSchema(schemaPath) {
6467
6617
  try {
6468
- return { exitCode: ExitCode.OK, result: ok(await readFile14(schemaPath, "utf8")) };
6618
+ return { exitCode: ExitCode.OK, result: ok(await readFile15(schemaPath, "utf8")) };
6469
6619
  } catch (error) {
6470
6620
  if (error.code === "ENOENT") {
6471
6621
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
@@ -6493,7 +6643,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
6493
6643
  };
6494
6644
  }
6495
6645
  async function reconcileTagsWhileLocked(input, page, tags, comment) {
6496
- const schemaPath = join27(input.vault, "SCHEMA.md");
6646
+ const schemaPath = join28(input.vault, "SCHEMA.md");
6497
6647
  const current = await readSchema(schemaPath);
6498
6648
  if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
6499
6649
  const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
@@ -6504,7 +6654,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
6504
6654
  }
6505
6655
  let verifiedText;
6506
6656
  try {
6507
- verifiedText = await readFile14(schemaPath, "utf8");
6657
+ verifiedText = await readFile15(schemaPath, "utf8");
6508
6658
  } catch (error) {
6509
6659
  return {
6510
6660
  exitCode: ExitCode.WRITE_FAILED,
@@ -6530,7 +6680,7 @@ async function runTagReconcile(input) {
6530
6680
  const comment = taxonomyCommentForPage(page.data, date, input.reason);
6531
6681
  if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
6532
6682
  if (!input.write) {
6533
- const schema = await readSchema(join27(input.vault, "SCHEMA.md"));
6683
+ const schema = await readSchema(join28(input.vault, "SCHEMA.md"));
6534
6684
  if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
6535
6685
  const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
6536
6686
  return previewResult(page.data, tags, preview, true, []);
@@ -6583,8 +6733,8 @@ async function runTagReconcile(input) {
6583
6733
 
6584
6734
  // src/commands/project-page-publish.ts
6585
6735
  import { realpathSync as realpathSync4 } from "fs";
6586
- import { mkdir as mkdir11, readFile as readFile15 } from "fs/promises";
6587
- import { dirname as dirname9, join as join29, resolve as resolve5 } from "path";
6736
+ import { mkdir as mkdir11, readFile as readFile16 } from "fs/promises";
6737
+ import { dirname as dirname9, join as join30, resolve as resolve5 } from "path";
6588
6738
 
6589
6739
  // src/utils/architecture-page.ts
6590
6740
  import { lstatSync, realpathSync as realpathSync2 } from "fs";
@@ -6745,7 +6895,7 @@ import {
6745
6895
  writeFileSync as writeFileSync5
6746
6896
  } from "fs";
6747
6897
  import { homedir } from "os";
6748
- import { dirname as dirname8, join as join28 } from "path";
6898
+ import { dirname as dirname8, join as join29 } from "path";
6749
6899
  var PUBLICATION_JOURNAL_SCHEMA = "skillwiki-publication-operation-v1";
6750
6900
  var PHASE_ORDER2 = [
6751
6901
  "locked",
@@ -6774,16 +6924,16 @@ function vaultIdentity(vaultPath) {
6774
6924
  return sha256Hex(real).slice(0, 16);
6775
6925
  }
6776
6926
  function publicationJournalRoot(home = process.env.HOME || homedir()) {
6777
- return join28(home, ".skillwiki", "publication-operations");
6927
+ return join29(home, ".skillwiki", "publication-operations");
6778
6928
  }
6779
6929
  function resolveJournalPaths(vaultPath, operationId2, home = process.env.HOME || homedir()) {
6780
6930
  const identity = vaultIdentity(vaultPath);
6781
6931
  const rootDir = publicationJournalRoot(home);
6782
- const vaultDir = join28(rootDir, identity);
6932
+ const vaultDir = join29(rootDir, identity);
6783
6933
  return {
6784
6934
  rootDir,
6785
6935
  vaultDir,
6786
- journalPath: join28(vaultDir, `${operationId2}.json`)
6936
+ journalPath: join29(vaultDir, `${operationId2}.json`)
6787
6937
  };
6788
6938
  }
6789
6939
  function ensurePrivateDir(dir) {
@@ -7068,7 +7218,7 @@ async function observeStage2(deps, stage) {
7068
7218
  }
7069
7219
  async function readPriorTargetSha(targetPath) {
7070
7220
  try {
7071
- const text = await readFile15(targetPath, "utf8");
7221
+ const text = await readFile16(targetPath, "utf8");
7072
7222
  return ok(sha256Hex(text));
7073
7223
  } catch (error) {
7074
7224
  if (error.code === "ENOENT") return ok("absent");
@@ -7093,7 +7243,7 @@ function validateLogNote(logNote) {
7093
7243
  async function prepareProjectPagePublication(input) {
7094
7244
  let content;
7095
7245
  try {
7096
- content = await readFile15(input.draftPath, "utf8");
7246
+ content = await readFile16(input.draftPath, "utf8");
7097
7247
  } catch (error) {
7098
7248
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
7099
7249
  }
@@ -7202,10 +7352,10 @@ function phaseFailure2(stage, prepared, published, verified, cause, context = {}
7202
7352
  };
7203
7353
  }
7204
7354
  async function previewProjectPagePublication(prepared, vault) {
7205
- const schemaPath = join29(vault, "SCHEMA.md");
7355
+ const schemaPath = join30(vault, "SCHEMA.md");
7206
7356
  let schemaText;
7207
7357
  try {
7208
- schemaText = await readFile15(schemaPath, "utf8");
7358
+ schemaText = await readFile16(schemaPath, "utf8");
7209
7359
  } catch (error) {
7210
7360
  return {
7211
7361
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -7228,18 +7378,18 @@ async function previewProjectPagePublication(prepared, vault) {
7228
7378
  }
7229
7379
  let projectIndexChanged = true;
7230
7380
  if (projectIndex.ok) {
7231
- const indexPath = join29(vault, projectIndex.data.index_path);
7381
+ const indexPath = join30(vault, projectIndex.data.index_path);
7232
7382
  try {
7233
- const existing = await readFile15(indexPath, "utf8");
7383
+ const existing = await readFile16(indexPath, "utf8");
7234
7384
  projectIndexChanged = existing !== projectIndex.data.text || pageChanged;
7235
7385
  } catch {
7236
7386
  projectIndexChanged = true;
7237
7387
  }
7238
7388
  }
7239
- const logPath = join29(vault, "log.md");
7389
+ const logPath = join30(vault, "log.md");
7240
7390
  let logAppended = true;
7241
7391
  try {
7242
- const logText = await readFile15(logPath, "utf8");
7392
+ const logText = await readFile16(logPath, "utf8");
7243
7393
  logAppended = !logText.includes(`<!-- skillwiki-project-page-publish:${prepared.operationId} -->`);
7244
7394
  } catch {
7245
7395
  return {
@@ -7363,11 +7513,11 @@ async function runLockedStages(prepared, vault, deps, journalHome) {
7363
7513
  if (journalHook) {
7364
7514
  return { ok: false, exitCode: ExitCode.WRITE_FAILED, stage: "journal", state, cause: journalHook };
7365
7515
  }
7366
- const schemaPath = join29(vault, "SCHEMA.md");
7516
+ const schemaPath = join30(vault, "SCHEMA.md");
7367
7517
  {
7368
7518
  let schemaText;
7369
7519
  try {
7370
- schemaText = await readFile15(schemaPath, "utf8");
7520
+ schemaText = await readFile16(schemaPath, "utf8");
7371
7521
  } catch (error) {
7372
7522
  return {
7373
7523
  ok: false,
@@ -7455,8 +7605,8 @@ async function runLockedStages(prepared, vault, deps, journalHome) {
7455
7605
  let visibleSchema;
7456
7606
  try {
7457
7607
  [visible, visibleSchema] = await Promise.all([
7458
- readFile15(prepared.targetPath, "utf8"),
7459
- readFile15(schemaPath, "utf8")
7608
+ readFile16(prepared.targetPath, "utf8"),
7609
+ readFile16(schemaPath, "utf8")
7460
7610
  ]);
7461
7611
  } catch (error) {
7462
7612
  return {
@@ -7510,7 +7660,7 @@ async function runLockedStages(prepared, vault, deps, journalHome) {
7510
7660
  cause: rendered
7511
7661
  };
7512
7662
  }
7513
- const knowledgePath = join29(vault, rendered.data.index_path);
7663
+ const knowledgePath = join30(vault, rendered.data.index_path);
7514
7664
  try {
7515
7665
  await mkdir11(dirname9(knowledgePath), { recursive: true });
7516
7666
  } catch (error) {
@@ -7535,7 +7685,7 @@ async function runLockedStages(prepared, vault, deps, journalHome) {
7535
7685
  state.projectIndexChanged = knowledgeWrite.data.changed;
7536
7686
  if (state.projectIndexChanged) state.changed.add(rendered.data.index_path);
7537
7687
  try {
7538
- const visibleIndex = await readFile15(knowledgePath, "utf8");
7688
+ const visibleIndex = await readFile16(knowledgePath, "utf8");
7539
7689
  if (visibleIndex !== rendered.data.text) {
7540
7690
  return {
7541
7691
  ok: false,
@@ -7909,7 +8059,7 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync4, appendFileSync } fr
7909
8059
  import { createHash as createHash5 } from "crypto";
7910
8060
  import { execSync as execSync3, spawn } from "child_process";
7911
8061
  import { platform as platform2 } from "os";
7912
- import { join as join30, resolve as resolvePath } from "path";
8062
+ import { join as join31, resolve as resolvePath } from "path";
7913
8063
  var MAINTENANCE_SCHEMA_VERSION = 1;
7914
8064
  var MAINTENANCE_COMMAND = "snapshot-maintenance journal clear-stale";
7915
8065
  function canonicalize(p) {
@@ -7972,7 +8122,7 @@ async function runSnapshotMaintenanceDryRun(input) {
7972
8122
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE"));
7973
8123
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
7974
8124
  }
7975
- if (!existsSync9(requested) || !existsSync9(join30(requested, ".git"))) {
8125
+ if (!existsSync9(requested) || !existsSync9(join31(requested, ".git"))) {
7976
8126
  audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO"));
7977
8127
  return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
7978
8128
  }
@@ -8052,14 +8202,14 @@ async function resolveLiveVault(input) {
8052
8202
  return resolved.ok ? canonicalize(resolved.data.path) : void 0;
8053
8203
  }
8054
8204
  function snapshotMaintenanceAuditLogPath(home) {
8055
- const stateDir = platform2() === "darwin" ? join30(home, "Library", "Application Support", "vault-sync") : join30(home, ".local", "state", "vault-sync");
8056
- return join30(stateDir, "snapshot-maintenance-audit.jsonl");
8205
+ const stateDir = platform2() === "darwin" ? join31(home, "Library", "Application Support", "vault-sync") : join31(home, ".local", "state", "vault-sync");
8206
+ return join31(stateDir, "snapshot-maintenance-audit.jsonl");
8057
8207
  }
8058
8208
  function defaultAuditSink(home) {
8059
8209
  return (event) => {
8060
8210
  try {
8061
8211
  const logPath = snapshotMaintenanceAuditLogPath(home);
8062
- mkdirSync4(join30(logPath, ".."), { recursive: true });
8212
+ mkdirSync4(join31(logPath, ".."), { recursive: true });
8063
8213
  appendFileSync(logPath, JSON.stringify(event) + "\n", { encoding: "utf8" });
8064
8214
  } catch {
8065
8215
  }
@@ -8220,7 +8370,7 @@ function releaseSnapshotFlock(handle) {
8220
8370
 
8221
8371
  // src/commands/backup.ts
8222
8372
  import { statSync as statSync3, readdirSync as readdirSync2, readFileSync as readFileSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
8223
- import { join as join31, relative as relative3, dirname as dirname10 } from "path";
8373
+ import { join as join32, relative as relative3, dirname as dirname10 } from "path";
8224
8374
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
8225
8375
 
8226
8376
  // src/utils/s3-client.ts
@@ -8244,7 +8394,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
8244
8394
  function* walkMarkdown(dir, base) {
8245
8395
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
8246
8396
  if (SKIP_DIRS.has(entry.name)) continue;
8247
- const full = join31(dir, entry.name);
8397
+ const full = join32(dir, entry.name);
8248
8398
  if (entry.isDirectory()) {
8249
8399
  yield* walkMarkdown(full, base);
8250
8400
  } else if (entry.name.endsWith(".md")) {
@@ -8267,7 +8417,7 @@ async function runBackupSync(input) {
8267
8417
  let failed = 0;
8268
8418
  const files = [...walkMarkdown(input.vault, input.vault)];
8269
8419
  for (const relPath of files) {
8270
- const absPath = join31(input.vault, relPath);
8420
+ const absPath = join32(input.vault, relPath);
8271
8421
  const localStat = statSync3(absPath);
8272
8422
  let needsUpload = true;
8273
8423
  try {
@@ -8343,7 +8493,7 @@ async function runBackupRestore(input) {
8343
8493
  const objects = list.Contents ?? [];
8344
8494
  for (const obj of objects) {
8345
8495
  if (!obj.Key) continue;
8346
- const localPath = join31(target, obj.Key);
8496
+ const localPath = join32(target, obj.Key);
8347
8497
  try {
8348
8498
  const localStat = statSync3(localPath);
8349
8499
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -8390,8 +8540,8 @@ async function runBackupRestore(input) {
8390
8540
 
8391
8541
  // src/commands/status.ts
8392
8542
  import { existsSync as existsSync10, statSync as statSync4 } from "fs";
8393
- import { readFile as readFile16 } from "fs/promises";
8394
- import { join as join32 } from "path";
8543
+ import { readFile as readFile17 } from "fs/promises";
8544
+ import { join as join33 } from "path";
8395
8545
  var FUSE_NO_MIRROR_NOTE = "FUSE vault with no read mirror - status scan may be slow";
8396
8546
  async function runStatus(input) {
8397
8547
  if (!existsSync10(input.vault)) {
@@ -8430,7 +8580,7 @@ async function runStatus(input) {
8430
8580
  const compound = scan.data.compound.length;
8431
8581
  let schemaVersion = "v1";
8432
8582
  try {
8433
- const schemaContent = await readFile16(join32(scanRoot, "SCHEMA.md"), "utf8");
8583
+ const schemaContent = await readFile17(join33(scanRoot, "SCHEMA.md"), "utf8");
8434
8584
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
8435
8585
  if (versionMatch) schemaVersion = versionMatch[1];
8436
8586
  } catch {
@@ -8498,7 +8648,7 @@ async function runStatus(input) {
8498
8648
 
8499
8649
  // src/commands/seed.ts
8500
8650
  import { mkdir as mkdir12, writeFile as writeFile8, stat as stat4 } from "fs/promises";
8501
- import { join as join33 } from "path";
8651
+ import { join as join34 } from "path";
8502
8652
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8503
8653
  var EXAMPLE_PAGES = {
8504
8654
  "entities/example-project.md": `---
@@ -8567,29 +8717,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
8567
8717
  `;
8568
8718
  async function runSeed(input) {
8569
8719
  try {
8570
- await stat4(join33(input.vault, "SCHEMA.md"));
8720
+ await stat4(join34(input.vault, "SCHEMA.md"));
8571
8721
  } catch {
8572
8722
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
8573
8723
  }
8574
8724
  const created = [];
8575
8725
  const skipped = [];
8576
8726
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
8577
- const absPath = join33(input.vault, relPath);
8727
+ const absPath = join34(input.vault, relPath);
8578
8728
  try {
8579
8729
  await stat4(absPath);
8580
8730
  skipped.push(relPath);
8581
8731
  } catch {
8582
- await mkdir12(join33(absPath, ".."), { recursive: true });
8732
+ await mkdir12(join34(absPath, ".."), { recursive: true });
8583
8733
  await writeFile8(absPath, content, "utf8");
8584
8734
  created.push(relPath);
8585
8735
  }
8586
8736
  }
8587
- const rawPath = join33(input.vault, "raw", "articles", "example-source.md");
8737
+ const rawPath = join34(input.vault, "raw", "articles", "example-source.md");
8588
8738
  try {
8589
8739
  await stat4(rawPath);
8590
8740
  skipped.push("raw/articles/example-source.md");
8591
8741
  } catch {
8592
- await mkdir12(join33(rawPath, ".."), { recursive: true });
8742
+ await mkdir12(join34(rawPath, ".."), { recursive: true });
8593
8743
  await writeFile8(rawPath, EXAMPLE_RAW, "utf8");
8594
8744
  created.push("raw/articles/example-source.md");
8595
8745
  }
@@ -8612,9 +8762,9 @@ async function runSeed(input) {
8612
8762
  }
8613
8763
 
8614
8764
  // src/commands/canvas.ts
8615
- import { readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
8765
+ import { readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
8616
8766
  import { existsSync as existsSync11 } from "fs";
8617
- import { join as join34 } from "path";
8767
+ import { join as join35 } from "path";
8618
8768
  var NODE_WIDTH = 240;
8619
8769
  var NODE_HEIGHT = 60;
8620
8770
  var COLUMN_SPACING = 400;
@@ -8692,7 +8842,7 @@ function buildCanvasEdges(adjacency) {
8692
8842
  return edges;
8693
8843
  }
8694
8844
  async function runCanvasGenerate(input) {
8695
- const graphPath = input.graphPath ?? join34(input.vault, ".skillwiki", "graph.json");
8845
+ const graphPath = input.graphPath ?? join35(input.vault, ".skillwiki", "graph.json");
8696
8846
  if (!existsSync11(graphPath)) {
8697
8847
  return {
8698
8848
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -8704,7 +8854,7 @@ async function runCanvasGenerate(input) {
8704
8854
  }
8705
8855
  let raw;
8706
8856
  try {
8707
- raw = await readFile17(graphPath, "utf8");
8857
+ raw = await readFile18(graphPath, "utf8");
8708
8858
  } catch (e) {
8709
8859
  return {
8710
8860
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -8730,7 +8880,7 @@ async function runCanvasGenerate(input) {
8730
8880
  const nodes = buildCanvasNodes(paths);
8731
8881
  const edges = buildCanvasEdges(graph.adjacency);
8732
8882
  const canvas = { nodes, edges };
8733
- const outPath = join34(input.vault, "vault-graph.canvas");
8883
+ const outPath = join35(input.vault, "vault-graph.canvas");
8734
8884
  try {
8735
8885
  await writeFile9(outPath, JSON.stringify(canvas, null, 2));
8736
8886
  } catch (e) {
@@ -8765,11 +8915,11 @@ async function runSourceDisposition(input) {
8765
8915
 
8766
8916
  // src/utils/source-disposal.ts
8767
8917
  import { createHash as createHash6 } from "crypto";
8768
- import { readFile as readFile19, unlink as unlink5 } from "fs/promises";
8918
+ import { readFile as readFile20, unlink as unlink5 } from "fs/promises";
8769
8919
 
8770
8920
  // src/utils/raw-asset-reference-index.ts
8771
- import { readdir as readdir7, readFile as readFile18 } from "fs/promises";
8772
- import { join as join35, relative as relative4, sep as sep3 } from "path";
8921
+ import { readdir as readdir7, readFile as readFile19 } from "fs/promises";
8922
+ import { join as join36, relative as relative4, sep as sep3 } from "path";
8773
8923
  var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
8774
8924
  async function walkFiles(root, dir = root) {
8775
8925
  let entries;
@@ -8780,7 +8930,7 @@ async function walkFiles(root, dir = root) {
8780
8930
  }
8781
8931
  const out = [];
8782
8932
  for (const entry of entries) {
8783
- const path = join35(dir, entry.name);
8933
+ const path = join36(dir, entry.name);
8784
8934
  if (entry.isDirectory()) out.push(...await walkFiles(root, path));
8785
8935
  else if (entry.isFile()) out.push(relative4(root, path).split(sep3).join("/"));
8786
8936
  }
@@ -8796,7 +8946,7 @@ function addInbound(map, target, source) {
8796
8946
  map.set(target, refs);
8797
8947
  }
8798
8948
  async function buildRawAssetReferenceIndex(vault) {
8799
- const assetRoot = join35(vault, "raw", "assets");
8949
+ const assetRoot = join36(vault, "raw", "assets");
8800
8950
  const relativeAssets = await walkFiles(assetRoot);
8801
8951
  const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
8802
8952
  const assetSet = new Set(assets);
@@ -8812,7 +8962,7 @@ async function buildRawAssetReferenceIndex(vault) {
8812
8962
  for (const page of scan.data.allMarkdown) {
8813
8963
  let text;
8814
8964
  try {
8815
- text = await readFile18(page.absPath, "utf8");
8965
+ text = await readFile19(page.absPath, "utf8");
8816
8966
  } catch {
8817
8967
  continue;
8818
8968
  }
@@ -8872,7 +9022,7 @@ async function planSourceDisposal(input) {
8872
9022
  if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
8873
9023
  const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
8874
9024
  if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
8875
- const bytes = await readFile19(target.data);
9025
+ const bytes = await readFile20(target.data);
8876
9026
  const size = bytes.byteLength;
8877
9027
  let typedReferences = [];
8878
9028
  let otherReferences = [];
@@ -8996,7 +9146,7 @@ async function runSourceDisposal(input) {
8996
9146
  import { existsSync as existsSync12, readFileSync as readFileSync16 } from "fs";
8997
9147
  import { execSync as nodeExecSync } from "child_process";
8998
9148
  import { hostname as nodeHostname, platform as nodePlatform } from "os";
8999
- import { join as join36 } from "path";
9149
+ import { join as join37 } from "path";
9000
9150
  var SSH_TIMEOUT_MS = 15e3;
9001
9151
  var TIMER_UNIT = "agent-memory-trends.timer";
9002
9152
  var SERVICE_UNIT = "agent-memory-trends.service";
@@ -9226,7 +9376,7 @@ async function runFleetHealth(input) {
9226
9376
  const home = input.home ?? env.HOME ?? "";
9227
9377
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
9228
9378
  const vault = input.vault ?? env.WIKI_PATH;
9229
- const file = input.file ?? (vault ? join36(vault, FLEET_REL_PATH) : void 0);
9379
+ const file = input.file ?? (vault ? join37(vault, FLEET_REL_PATH) : void 0);
9230
9380
  if (!file) {
9231
9381
  return {
9232
9382
  exitCode: ExitCode.NO_VAULT_CONFIGURED,
@@ -9311,14 +9461,14 @@ async function runFleetHealth(input) {
9311
9461
 
9312
9462
  // src/utils/auto-commit.ts
9313
9463
  import { existsSync as existsSync13 } from "fs";
9314
- import { join as join37 } from "path";
9464
+ import { join as join38 } from "path";
9315
9465
  async function postCommit(vault, exitCode) {
9316
9466
  if (exitCode !== 0) return;
9317
9467
  const home = process.env.HOME ?? "";
9318
9468
  const dotenv = await parseDotenvFile(configPath(home));
9319
9469
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
9320
9470
  if (autoCommit === "false") return;
9321
- if (!existsSync13(join37(vault, ".git"))) return;
9471
+ if (!existsSync13(join38(vault, ".git"))) return;
9322
9472
  const lastOps = readLastOp(vault);
9323
9473
  if (lastOps.length === 0) return;
9324
9474
  const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
@@ -9479,7 +9629,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
9479
9629
  if (dirty) {
9480
9630
  return emit(dirty, void 0, { postCommit: false });
9481
9631
  }
9482
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-DWK3PJCO.js");
9632
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-H7KVK2KI.js");
9483
9633
  const run = await runManagedWriteTransaction2({
9484
9634
  vault,
9485
9635
  command,
@@ -9509,7 +9659,7 @@ program.command("validate <file>").description("validate vault page frontmatter
9509
9659
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
9510
9660
  });
9511
9661
  program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
9512
- const out = opts.out ?? join38(vault, ".skillwiki", "graph.json");
9662
+ const out = opts.out ?? join39(vault, ".skillwiki", "graph.json");
9513
9663
  return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
9514
9664
  });
9515
9665
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -10124,6 +10274,12 @@ program.command("project-index <slug> [vault]").description("generate a knowledg
10124
10274
  );
10125
10275
  else emit(await runProjectIndex({ vault: v.vault, slug, apply: false, check: opts.check }), v.vault);
10126
10276
  });
10277
+ var claimsCmd = program.command("claims").description("audit raw-transcript claim integrity (read-only)");
10278
+ claimsCmd.command("audit [vault]").description("report duplicate, malformed, dangling, project-mismatch, and work-item-unbacked claims (read-only)").option("--project <slug>", "scope findings to an exact project slug").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
10279
+ const v = await resolveVaultArg(vault, opts.wiki);
10280
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
10281
+ else emit(await runClaimsAudit({ vault: v.vault, project: opts.project }), v.vault, { postCommit: false });
10282
+ });
10127
10283
  var compoundCmd = program.command("compound").description("manage project compound entries");
10128
10284
  compoundCmd.command("promote [vault]").description("promote retros with Generalize?: yes to compound entries").requiredOption("--project <slug>", "project slug (e.g., llm-wiki)").option("--dry-run", "preview promotions without writing files", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
10129
10285
  const v = await resolveVaultArg(vault, opts.wiki);