skillwiki 0.10.56 → 0.10.58

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.
@@ -3305,7 +3305,7 @@ function buildCliSurface() {
3305
3305
  program.command("install").option("--target <dir>").option("--dry-run").option("--skills-root <dir>").option("--symlink");
3306
3306
  program.command("path").option("--vault <dir>").option("--target <dir>").option("--wiki <name>").option("--init-time").option("--explain").option("--plain");
3307
3307
  program.command("lang").option("--lang <code>").option("--explain");
3308
- program.command("init").option("--target <dir>").requiredOption("--domain <text>").option("--taxonomy <csv>").option("--lang <code>").option("--force").option("--no-env").option("--profile <name>");
3308
+ program.command("init").option("--target <dir>").requiredOption("--domain <text>").option("--taxonomy <csv>").option("--lang <code>").option("--force").option("--no-env").option("--profile <name>").option("--no-gitignore").option("--write-gitignore");
3309
3309
  program.command("links").option("--wiki <name>");
3310
3310
  program.command("tag-audit").option("--wiki <name>");
3311
3311
  program.command("index-check").option("--wiki <name>");
@@ -5497,23 +5497,76 @@ function releaseOwnedSyncLock(handle) {
5497
5497
  }
5498
5498
  }
5499
5499
 
5500
- // src/utils/vault-git-pathspec.ts
5501
- var VAULT_GENERATED_COMMIT_PATHS = [
5500
+ // src/utils/vault-hygiene-ignores.ts
5501
+ var VAULT_HYGIENE_GITIGNORE_PATTERNS = [
5502
+ ".skillwiki/last-op.json",
5503
+ ".skillwiki/graph.json",
5504
+ ".skillwiki/sync.lock",
5505
+ ".skillwiki/managed-write.lock",
5506
+ ".skillwiki/memory/",
5507
+ ".skillwiki/memory-topics.json",
5508
+ ".skillwiki/work-complete/",
5509
+ ".skillwiki/vectors/"
5510
+ ];
5511
+ var VAULT_HYGIENE_GENERATED_COMMIT_PATHS = VAULT_HYGIENE_GITIGNORE_PATTERNS.map(
5512
+ (pattern) => pattern.replace(/\/$/, "")
5513
+ );
5514
+ var VAULT_SYNC_FILTER_REQUIRED_EXCLUDES = [
5515
+ "remotely-save/data.json",
5516
+ ".skillwiki/sync.lock",
5517
+ ".skillwiki/managed-write.lock",
5518
+ ".skillwiki/graph.json",
5519
+ ".skillwiki/memory/",
5520
+ ".skillwiki/memory-topics.json",
5521
+ ".skillwiki/work-complete/",
5502
5522
  ".skillwiki/last-op.json",
5503
- ".skillwiki/memory",
5504
- ".skillwiki/memory-topics.json"
5523
+ ".claude/settings.local.json"
5505
5524
  ];
5525
+ function missingIgnorePatterns(content, patterns) {
5526
+ return patterns.filter((pattern) => !content.includes(pattern));
5527
+ }
5528
+ function mergeGitignore(existing, required = VAULT_HYGIENE_GITIGNORE_PATTERNS) {
5529
+ const added = missingIgnorePatterns(existing, required);
5530
+ if (added.length === 0) {
5531
+ return { text: existing, changed: false, added: [] };
5532
+ }
5533
+ const base = existing.length === 0 || existing.endsWith("\n") ? existing : `${existing}
5534
+ `;
5535
+ const block = [
5536
+ "# SkillWiki local scratch (not GitHub SSOT; keep session-brief and agent-memory-trends)",
5537
+ ...added,
5538
+ ""
5539
+ ].join("\n");
5540
+ return { text: `${base}${block}`, changed: true, added };
5541
+ }
5542
+ function renderVaultGitignoreTemplate() {
5543
+ return [
5544
+ "# SkillWiki vault gitignore",
5545
+ "# Local scratch must not enter GitHub. Keep session-brief.* and agent-memory-trends/.",
5546
+ ...VAULT_HYGIENE_GITIGNORE_PATTERNS,
5547
+ ".obsidian/workspace.json",
5548
+ "*.conflict-*",
5549
+ ".conflict*",
5550
+ ".claude/settings.local.json",
5551
+ "._.DS_Store",
5552
+ ".DS_Store",
5553
+ "logs",
5554
+ "tmp/",
5555
+ ""
5556
+ ].join("\n");
5557
+ }
5558
+
5559
+ // src/utils/vault-git-pathspec.ts
5560
+ var VAULT_GENERATED_COMMIT_PATHS = VAULT_HYGIENE_GENERATED_COMMIT_PATHS;
5506
5561
  var VAULT_GENERATED_COMMIT_EXCLUDES = [
5507
5562
  ...VAULT_GENERATED_COMMIT_PATHS.map((path) => `:!${path}`)
5508
5563
  ];
5509
5564
  var VAULT_COMMIT_PATHSPEC = [".", ...VAULT_GENERATED_COMMIT_EXCLUDES];
5510
5565
  function stageVaultContentChanges(vault) {
5511
5566
  gitStrict(vault, ["add", "-A", "--", "."]);
5512
- for (const generatedPath of VAULT_GENERATED_COMMIT_PATHS) {
5513
- try {
5514
- gitStrict(vault, ["reset", "HEAD", "--", generatedPath]);
5515
- } catch (_e) {
5516
- }
5567
+ try {
5568
+ gitStrict(vault, ["reset", "HEAD", "--", ...VAULT_GENERATED_COMMIT_PATHS]);
5569
+ } catch (_e) {
5517
5570
  }
5518
5571
  }
5519
5572
 
@@ -7231,6 +7284,12 @@ export {
7231
7284
  defaultLintRunner,
7232
7285
  runLint,
7233
7286
  runSyncLintDelta,
7287
+ VAULT_HYGIENE_GITIGNORE_PATTERNS,
7288
+ VAULT_HYGIENE_GENERATED_COMMIT_PATHS,
7289
+ VAULT_SYNC_FILTER_REQUIRED_EXCLUDES,
7290
+ missingIgnorePatterns,
7291
+ mergeGitignore,
7292
+ renderVaultGitignoreTemplate,
7234
7293
  FLEET_REL_PATH,
7235
7294
  runFleetValidate,
7236
7295
  runFleetContext,
@@ -9,6 +9,9 @@ import {
9
9
  } from "./chunk-NHRRYAXT.js";
10
10
  import {
11
11
  CONFIG_KEYS,
12
+ VAULT_HYGIENE_GENERATED_COMMIT_PATHS,
13
+ VAULT_HYGIENE_GITIGNORE_PATTERNS,
14
+ VAULT_SYNC_FILTER_REQUIRED_EXCLUDES,
12
15
  acquireOwnedSyncLock,
13
16
  appendLastOp,
14
17
  buildWikilinkAdjacency,
@@ -18,6 +21,7 @@ import {
18
21
  listReviewRequiredOps,
19
22
  loadFleetManifestAndHost,
20
23
  louvain,
24
+ missingIgnorePatterns,
21
25
  parseDotenvFile,
22
26
  parseDotenvText,
23
27
  probeGithubReachability,
@@ -35,7 +39,7 @@ import {
35
39
  snapshotterAliasForLocalHost,
36
40
  toUndirectedWeighted,
37
41
  writeDotenv
38
- } from "./chunk-FM6RWVW6.js";
42
+ } from "./chunk-LXZPRAZU.js";
39
43
  import {
40
44
  atomicWriteText,
41
45
  prepareTypedPage
@@ -1767,7 +1771,7 @@ var gitFleetProbe = {
1767
1771
  };
1768
1772
 
1769
1773
  // src/doctor/probes/hygiene.ts
1770
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
1774
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
1771
1775
  import { join as join11 } from "path";
1772
1776
 
1773
1777
  // src/utils/conflict-markers.ts
@@ -1894,12 +1898,60 @@ function checkVaultConflictMarkers(resolvedPath) {
1894
1898
  `${n} ${fileWord}, first: ${first.path}:${first.line}`
1895
1899
  );
1896
1900
  }
1901
+ function githubSyncedGitRoot(gitRoot) {
1902
+ if (gitRoot === void 0) return { ok: false, reason: "No vault path \u2014 check skipped" };
1903
+ if (!existsSync8(join11(gitRoot, ".git"))) return { ok: false, reason: "Not a git repository \u2014 check skipped" };
1904
+ if (!git(gitRoot, ["remote"])) return { ok: false, reason: "No git remote \u2014 not GitHub-synced, check skipped" };
1905
+ return { ok: true, gitRoot };
1906
+ }
1907
+ function checkVaultGitignoreHygiene(synced) {
1908
+ if (!synced.ok) {
1909
+ return check("pass", "vault_gitignore_hygiene", "Vault gitignore hygiene", synced.reason);
1910
+ }
1911
+ let content = "";
1912
+ try {
1913
+ content = readFileSync5(join11(synced.gitRoot, ".gitignore"), "utf8");
1914
+ } catch {
1915
+ content = "";
1916
+ }
1917
+ const missing = missingIgnorePatterns(content, VAULT_HYGIENE_GITIGNORE_PATTERNS);
1918
+ if (missing.length === 0) {
1919
+ return check("pass", "vault_gitignore_hygiene", "Vault gitignore hygiene", "Required local-scratch patterns present");
1920
+ }
1921
+ return check(
1922
+ "warn",
1923
+ "vault_gitignore_hygiene",
1924
+ "Vault gitignore hygiene",
1925
+ `Missing ${missing.join(", ")} \u2014 run \`skillwiki init --target <vault> --domain existing --write-gitignore\``
1926
+ );
1927
+ }
1928
+ function checkTrackedHygieneScratch(synced) {
1929
+ if (!synced.ok) {
1930
+ return check("pass", "vault_gitignore_tracked_scratch", "Tracked hygiene scratch", synced.reason);
1931
+ }
1932
+ const listed = git(synced.gitRoot, ["ls-files", "--", ...VAULT_HYGIENE_GENERATED_COMMIT_PATHS]);
1933
+ const files = listed ? listed.split("\n").filter(Boolean) : [];
1934
+ if (files.length === 0) {
1935
+ return check("pass", "vault_gitignore_tracked_scratch", "Tracked hygiene scratch", "No local-scratch paths tracked");
1936
+ }
1937
+ const sample = files.slice(0, 3).join(", ");
1938
+ const more = files.length > 3 ? ` (+${files.length - 3} more)` : "";
1939
+ return check(
1940
+ "warn",
1941
+ "vault_gitignore_tracked_scratch",
1942
+ "Tracked hygiene scratch",
1943
+ `${files.length} tracked scratch path(s) (${sample}${more}) \u2014 untrack with: git rm --cached -- ${files[0]}`
1944
+ );
1945
+ }
1897
1946
  var hygieneProbe = {
1898
1947
  id: "hygiene",
1899
1948
  run(ctx) {
1949
+ const synced = githubSyncedGitRoot(ctx.gitCheckPath);
1900
1950
  return [
1901
1951
  checkDotStoreClean(ctx.readOnlyScanRoot),
1902
- checkVaultConflictMarkers(ctx.readOnlyScanRoot)
1952
+ checkVaultConflictMarkers(ctx.readOnlyScanRoot),
1953
+ checkVaultGitignoreHygiene(synced),
1954
+ checkTrackedHygieneScratch(synced)
1903
1955
  ];
1904
1956
  }
1905
1957
  };
@@ -1912,7 +1964,7 @@ import { execSync as execSync4 } from "child_process";
1912
1964
  // src/utils/s3-mount-health.ts
1913
1965
  import { execSync as execSync3 } from "child_process";
1914
1966
  import { platform as platform2 } from "os";
1915
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, readFileSync as readFile6 } from "fs";
1967
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, readFileSync as readFile6 } from "fs";
1916
1968
  import { join as join12 } from "path";
1917
1969
  var OS = platform2();
1918
1970
  function findRcloneMountPid() {
@@ -1997,7 +2049,7 @@ function extractRcloneFs(args) {
1997
2049
  function getRcloneArgs(pid) {
1998
2050
  try {
1999
2051
  if (OS === "linux") {
2000
- const raw = readFileSync5(`/proc/${pid}/cmdline`);
2052
+ const raw = readFileSync6(`/proc/${pid}/cmdline`);
2001
2053
  return new TextDecoder().decode(raw).split("\0").filter(Boolean);
2002
2054
  } else {
2003
2055
  const out = execSync3(`ps -o args= -p ${pid}`, {
@@ -2040,7 +2092,7 @@ function queryRcloneRC(rcAddr, fs) {
2040
2092
  function detectFuseMount(vaultPath) {
2041
2093
  try {
2042
2094
  if (OS === "linux") {
2043
- const mounts = readFileSync5("/proc/mounts", "utf8");
2095
+ const mounts = readFileSync6("/proc/mounts", "utf8");
2044
2096
  let best = null;
2045
2097
  for (const line of mounts.split("\n")) {
2046
2098
  const parts = line.split(" ");
@@ -2435,7 +2487,7 @@ var s3MountHealthProbe = {
2435
2487
  };
2436
2488
 
2437
2489
  // src/doctor/probes/skills-plugins.ts
2438
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "fs";
2490
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
2439
2491
  import { join as join14 } from "path";
2440
2492
  function findSkillMd(dir) {
2441
2493
  const results = [];
@@ -2571,7 +2623,7 @@ function checkGrokActivation(home, cwd) {
2571
2623
  if (!existsSync11(agentsPath)) {
2572
2624
  issues.push("~/.grok/AGENTS.md missing");
2573
2625
  } else {
2574
- const agents = readFileSync6(agentsPath, "utf8");
2626
+ const agents = readFileSync7(agentsPath, "utf8");
2575
2627
  const hasBegin = agents.includes("<!-- skillwiki:begin -->");
2576
2628
  const hasExpected = agents.includes(GROK_ACTIVATION_REFERENCE);
2577
2629
  const hasStale = agents.includes(STALE_GROK_ACTIVATION_REFERENCE);
@@ -2587,8 +2639,8 @@ function checkGrokActivation(home, cwd) {
2587
2639
  const template = findGrokActivationTemplate(home, cwd);
2588
2640
  if (template) {
2589
2641
  try {
2590
- const installed = readFileSync6(activationPath, "utf8");
2591
- const expected = readFileSync6(template, "utf8");
2642
+ const installed = readFileSync7(activationPath, "utf8");
2643
+ const expected = readFileSync7(template, "utf8");
2592
2644
  if (installed !== expected) {
2593
2645
  issues.push("~/.grok/skillwiki.md differs from template");
2594
2646
  }
@@ -2673,14 +2725,14 @@ var skillsPluginsProbe = {
2673
2725
  };
2674
2726
 
2675
2727
  // src/doctor/probes/vault-sync.ts
2676
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
2728
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
2677
2729
  import { join as join15 } from "path";
2678
2730
  import { execSync as execSync5 } from "child_process";
2679
2731
  import { platform as platform3 } from "os";
2680
2732
  function readPushResultState(stateFile) {
2681
2733
  if (!existsSync12(stateFile)) return { exists: false };
2682
2734
  try {
2683
- const content = readFileSync7(stateFile, "utf8");
2735
+ const content = readFileSync8(stateFile, "utf8");
2684
2736
  let result;
2685
2737
  let reason;
2686
2738
  let timestamp;
@@ -2703,7 +2755,7 @@ function readPushResultState(stateFile) {
2703
2755
  }
2704
2756
  function readVaultSyncConfig(home) {
2705
2757
  try {
2706
- const content = readFileSync7(join15(home, ".skillwiki", ".env"), "utf8");
2758
+ const content = readFileSync8(join15(home, ".skillwiki", ".env"), "utf8");
2707
2759
  let installed = false;
2708
2760
  let role;
2709
2761
  let serviceScope;
@@ -2739,7 +2791,7 @@ function loadSnapshotFixture(env) {
2739
2791
  const path = env.VS_SNAPSHOT_HEALTH_FIXTURE;
2740
2792
  if (!path || !existsSync12(path)) return null;
2741
2793
  try {
2742
- return JSON.parse(readFileSync7(path, "utf8"));
2794
+ return JSON.parse(readFileSync8(path, "utf8"));
2743
2795
  } catch {
2744
2796
  return null;
2745
2797
  }
@@ -2805,7 +2857,7 @@ function checkPushAgeFromTimestamp(ts) {
2805
2857
  }
2806
2858
  function checkPushAgeFromLog(logDir, logFile) {
2807
2859
  try {
2808
- const logContent = readFileSync7(logFile, "utf8");
2860
+ const logContent = readFileSync8(logFile, "utf8");
2809
2861
  const lines = logContent.trim().split("\n").filter(Boolean);
2810
2862
  if (lines.length === 0) {
2811
2863
  return check(
@@ -2906,7 +2958,7 @@ function snapshotterHealthChecks(scope, logDir, env) {
2906
2958
  let completionOutcome = "unknown";
2907
2959
  const logRecords = fixture ? fixture.log_records : (() => {
2908
2960
  try {
2909
- const content = readFileSync7(join15(logDir, "wiki-snapshot.log"), "utf8");
2961
+ const content = readFileSync8(join15(logDir, "wiki-snapshot.log"), "utf8");
2910
2962
  return content.split(/\r?\n/).filter(Boolean);
2911
2963
  } catch {
2912
2964
  return [];
@@ -3010,7 +3062,7 @@ function vaultSyncChecks(input) {
3010
3062
  `Snapshot script not found at ${snapshotPath}`
3011
3063
  );
3012
3064
  } else {
3013
- const content = readFileSync7(snapshotPath, "utf8");
3065
+ const content = readFileSync8(snapshotPath, "utf8");
3014
3066
  if (!content.includes("--max-delete")) {
3015
3067
  c52 = check(
3016
3068
  "error",
@@ -3144,7 +3196,7 @@ function vaultSyncChecks(input) {
3144
3196
  const fetchLogFile = join15(logDir, "wiki-fetch.log");
3145
3197
  let cFetch;
3146
3198
  try {
3147
- const logContent = readFileSync7(fetchLogFile, "utf8");
3199
+ const logContent = readFileSync8(fetchLogFile, "utf8");
3148
3200
  const lines = logContent.trim().split("\n").filter(Boolean);
3149
3201
  if (lines.length === 0) {
3150
3202
  cFetch = check(
@@ -3196,15 +3248,8 @@ function vaultSyncChecks(input) {
3196
3248
  `Filter file not found at ${filterPath}`
3197
3249
  );
3198
3250
  } else {
3199
- const content = readFileSync7(filterPath, "utf8");
3200
- const requiredExcludes = [
3201
- "remotely-save/data.json",
3202
- ".skillwiki/sync.lock",
3203
- ".skillwiki/memory/",
3204
- ".skillwiki/memory-topics.json",
3205
- ".claude/settings.local.json"
3206
- ];
3207
- const missing = requiredExcludes.filter((ex) => !content.includes(ex));
3251
+ const content = readFileSync8(filterPath, "utf8");
3252
+ const missing = VAULT_SYNC_FILTER_REQUIRED_EXCLUDES.filter((ex) => !content.includes(ex));
3208
3253
  if (missing.length > 0) {
3209
3254
  c4 = check(
3210
3255
  "warn",
@@ -3247,7 +3292,7 @@ function vaultSyncChecks(input) {
3247
3292
  `Snapshot script not found at ${snapshotPath}`
3248
3293
  );
3249
3294
  } else {
3250
- const content = readFileSync7(snapshotPath, "utf8");
3295
+ const content = readFileSync8(snapshotPath, "utf8");
3251
3296
  if (!content.includes("--max-delete")) {
3252
3297
  c5 = check(
3253
3298
  "error",
@@ -3335,7 +3380,7 @@ import { execSync as execSync6 } from "child_process";
3335
3380
  import { platform as platform4 } from "os";
3336
3381
 
3337
3382
  // src/utils/satellite-run-health.ts
3338
- import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
3383
+ import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
3339
3384
  import { join as join16 } from "path";
3340
3385
  var SATELLITE_STALE_MS = 26 * 60 * 60 * 1e3;
3341
3386
  function satelliteLatestRunPath(vault) {
@@ -3363,7 +3408,7 @@ function readSatelliteLatestRun(vault) {
3363
3408
  const latestPath = satelliteLatestRunPath(vault);
3364
3409
  if (!existsSync13(latestPath)) return null;
3365
3410
  try {
3366
- return parseLatestRunFile(readFileSync8(latestPath, "utf8"));
3411
+ return parseLatestRunFile(readFileSync9(latestPath, "utf8"));
3367
3412
  } catch {
3368
3413
  return null;
3369
3414
  }
@@ -3474,7 +3519,7 @@ var satelliteProbe = {
3474
3519
  };
3475
3520
 
3476
3521
  // src/doctor/probes/metrics.ts
3477
- import { readFileSync as readFileSync9 } from "fs";
3522
+ import { readFileSync as readFileSync10 } from "fs";
3478
3523
  import { join as join17 } from "path";
3479
3524
  var METRIC_TYPES = ["entities", "concepts", "comparisons", "queries", "meta"];
3480
3525
  function doctorReadOnlyScanRoot(resolvedPath) {
@@ -3519,7 +3564,7 @@ async function vaultMetrics(resolvedPath) {
3519
3564
  }
3520
3565
  let logLines = 0;
3521
3566
  try {
3522
- logLines = readFileSync9(join17(scanRoot, "log.md"), "utf8").split("\n").length;
3567
+ logLines = readFileSync10(join17(scanRoot, "log.md"), "utf8").split("\n").length;
3523
3568
  } catch {
3524
3569
  }
3525
3570
  return [
@@ -3654,7 +3699,7 @@ var fuseStalenessProbe = {
3654
3699
  };
3655
3700
 
3656
3701
  // src/doctor/probes/activation-marker.ts
3657
- import { existsSync as existsSync15, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "fs";
3702
+ import { existsSync as existsSync15, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
3658
3703
  import { join as join18 } from "path";
3659
3704
  var GROK_ACTIVATION_REFERENCE2 = "Read @~/.grok/skillwiki.md for SkillWiki activation context.";
3660
3705
  var STALE_GROK_ACTIVATION_REFERENCE2 = "Read @skillwiki.md";
@@ -3700,7 +3745,7 @@ function checkActivationMarker(home, cwd) {
3700
3745
  issues.push("~/.grok/AGENTS.md missing");
3701
3746
  } else {
3702
3747
  try {
3703
- const agents = readFileSync10(agentsPath, "utf8");
3748
+ const agents = readFileSync11(agentsPath, "utf8");
3704
3749
  const hasBegin = agents.includes("<!-- skillwiki:begin -->");
3705
3750
  const hasExpected = agents.includes(GROK_ACTIVATION_REFERENCE2);
3706
3751
  const hasStale = agents.includes(STALE_GROK_ACTIVATION_REFERENCE2);
@@ -3719,8 +3764,8 @@ function checkActivationMarker(home, cwd) {
3719
3764
  const template = findGrokActivationTemplate2(home, cwd);
3720
3765
  if (template) {
3721
3766
  try {
3722
- const installed = readFileSync10(activationPath, "utf8");
3723
- const expected = readFileSync10(template, "utf8");
3767
+ const installed = readFileSync11(activationPath, "utf8");
3768
+ const expected = readFileSync11(template, "utf8");
3724
3769
  if (installed !== expected) {
3725
3770
  issues.push("~/.grok/skillwiki.md differs from template");
3726
3771
  }
@@ -3901,7 +3946,7 @@ async function runDoctor(input) {
3901
3946
  }
3902
3947
 
3903
3948
  // src/utils/package-info.ts
3904
- import { readFileSync as readFileSync11 } from "fs";
3949
+ import { readFileSync as readFileSync12 } from "fs";
3905
3950
  function packageJsonCandidateUrls(baseUrl = import.meta.url) {
3906
3951
  return [
3907
3952
  new URL("../package.json", baseUrl),
@@ -3911,7 +3956,7 @@ function packageJsonCandidateUrls(baseUrl = import.meta.url) {
3911
3956
  function readCliPackageJson(baseUrl = import.meta.url) {
3912
3957
  for (const url of packageJsonCandidateUrls(baseUrl)) {
3913
3958
  try {
3914
- const pkg = JSON.parse(readFileSync11(url, "utf8"));
3959
+ const pkg = JSON.parse(readFileSync12(url, "utf8"));
3915
3960
  if (typeof pkg.version === "string") {
3916
3961
  return { ...pkg, version: pkg.version };
3917
3962
  }
@@ -3923,7 +3968,7 @@ function readCliPackageJson(baseUrl = import.meta.url) {
3923
3968
 
3924
3969
  // src/utils/vault-write-gates.ts
3925
3970
  import { execFileSync } from "child_process";
3926
- import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
3971
+ import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
3927
3972
  import { join as join20, relative as relative2 } from "path";
3928
3973
  var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
3929
3974
  var DEFAULT_CAPTURE_BUDGET = 20;
@@ -4173,7 +4218,7 @@ function listProjectDayCaptures(vault, project, day) {
4173
4218
  }
4174
4219
  if (norm.startsWith("raw/transcripts/")) {
4175
4220
  try {
4176
- const body = readFileSync12(abs, "utf8");
4221
+ const body = readFileSync13(abs, "utf8");
4177
4222
  if (body.includes(`project: ${slug}`) || body.includes(`project: "[[${slug}]]"`) || body.includes(`project: [[${slug}]]`)) {
4178
4223
  found.push(norm);
4179
4224
  } else if (base.includes(slug)) {
package/dist/cli.js CHANGED
@@ -50,7 +50,7 @@ import {
50
50
  snapshotterHealthChecks,
51
51
  upsertIndexEntry,
52
52
  vectorIndexStatus
53
- } from "./chunk-AVVFFPQ5.js";
53
+ } from "./chunk-Y2EPEJT7.js";
54
54
  import {
55
55
  normalizeDistTag,
56
56
  readCache,
@@ -69,6 +69,8 @@ import {
69
69
  REDACTED_MALFORMED_REFERENCE,
70
70
  VAULT_COMMIT_PATHSPEC,
71
71
  VAULT_GENERATED_COMMIT_PATHS,
72
+ VAULT_HYGIENE_GITIGNORE_PATTERNS,
73
+ VAULT_SYNC_FILTER_REQUIRED_EXCLUDES,
72
74
  acquireOwnedSyncLock,
73
75
  appendLastOp,
74
76
  applyRawStructuralMove,
@@ -95,6 +97,7 @@ import {
95
97
  loadFleetManifest,
96
98
  loadFleetManifestAndHost,
97
99
  markJournalSuperseded,
100
+ mergeGitignore,
98
101
  mergeTaxonomyConflict,
99
102
  normalizeProjectSlug,
100
103
  normalizeRemoteRoot,
@@ -109,6 +112,7 @@ import {
109
112
  readLastOp,
110
113
  reconcileTaxonomyDocument,
111
114
  releaseOwnedSyncLock,
115
+ renderVaultGitignoreTemplate,
112
116
  resolveConfiguredSnapshotWorktree,
113
117
  resolveFleetHostId,
114
118
  resolveInitTimePath,
@@ -147,7 +151,7 @@ import {
147
151
  supersedeStaleReviewRequiredJournals,
148
152
  taxonomyCommentForPage,
149
153
  writeDotenv
150
- } from "./chunk-FM6RWVW6.js";
154
+ } from "./chunk-LXZPRAZU.js";
151
155
  import {
152
156
  assertTargetInsideVault,
153
157
  atomicWriteText,
@@ -1019,6 +1023,23 @@ async function discoverTagsFromPages(target, knownSlugs) {
1019
1023
  }
1020
1024
  return [...discovered].sort();
1021
1025
  }
1026
+ async function applyVaultGitignore(target) {
1027
+ const dest = join6(target, ".gitignore");
1028
+ let existing = "";
1029
+ let existed = false;
1030
+ try {
1031
+ existing = await readFile3(dest, "utf8");
1032
+ existed = true;
1033
+ } catch {
1034
+ }
1035
+ const next = existed ? mergeGitignore(existing, VAULT_HYGIENE_GITIGNORE_PATTERNS) : { text: renderVaultGitignoreTemplate(), changed: true, added: [...VAULT_HYGIENE_GITIGNORE_PATTERNS] };
1036
+ if (!next.changed) {
1037
+ return { written: false, preserved: true };
1038
+ }
1039
+ await mkdir3(dirname3(dest), { recursive: true });
1040
+ await writeFile2(dest, next.text, "utf8");
1041
+ return { written: true, preserved: existed };
1042
+ }
1022
1043
  async function runInit(input) {
1023
1044
  const pathRes = await resolveInitTimePath({ flag: input.flag, envValue: input.envValue, home: input.home });
1024
1045
  const target = pathRes.path;
@@ -1030,6 +1051,37 @@ async function runInit(input) {
1030
1051
  } catch {
1031
1052
  }
1032
1053
  if (oldSchemaText && !input.force) {
1054
+ if (input.writeGitignoreOnly && !input.noGitignore) {
1055
+ try {
1056
+ const gi = await applyVaultGitignore(target);
1057
+ return {
1058
+ exitCode: ExitCode.OK,
1059
+ result: ok({
1060
+ vault: target,
1061
+ domain: input.domain,
1062
+ taxonomy: input.taxonomy && input.taxonomy.length > 0 ? input.taxonomy : DEFAULT_TAXONOMY,
1063
+ lang: canonicalLang,
1064
+ created: gi.written ? [".gitignore"] : [],
1065
+ preserved: gi.preserved ? [".gitignore"] : [],
1066
+ env_written: "",
1067
+ env_skipped: true,
1068
+ imported_from_hermes: false,
1069
+ discovered_tags: 0,
1070
+ humanHint: `vault: ${target}
1071
+ gitignore: ${gi.written ? "merged" : "unchanged"} (write-gitignore only)`,
1072
+ templates_created: false,
1073
+ web_clipper_template_path: WEB_CLIPPER_TEMPLATE_REL,
1074
+ web_clipper_readme_path: WEB_CLIPPER_README_REL,
1075
+ web_clipper_template_created: false,
1076
+ web_clipper_template_preserved: false,
1077
+ gitignore_written: gi.written,
1078
+ gitignore_preserved: gi.preserved
1079
+ })
1080
+ };
1081
+ } catch (e) {
1082
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: ".gitignore", message: String(e) }) };
1083
+ }
1084
+ }
1033
1085
  return {
1034
1086
  exitCode: ExitCode.INIT_TARGET_NOT_EMPTY,
1035
1087
  result: err("INIT_TARGET_NOT_EMPTY", { target })
@@ -1194,6 +1246,19 @@ async function runInit(input) {
1194
1246
  }
1195
1247
  }
1196
1248
  const importedFromHermes = pathRes.source === "hermes-dotenv" && !swDotenvHadPath;
1249
+ let gitignoreWritten = false;
1250
+ let gitignorePreserved = false;
1251
+ if (!input.noGitignore) {
1252
+ try {
1253
+ const gi = await applyVaultGitignore(target);
1254
+ gitignoreWritten = gi.written;
1255
+ gitignorePreserved = gi.preserved;
1256
+ if (gi.written) created.push(".gitignore");
1257
+ else if (gi.preserved) preserved.push(".gitignore");
1258
+ } catch (e) {
1259
+ return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: ".gitignore", message: String(e) }) };
1260
+ }
1261
+ }
1197
1262
  const humanHint = [
1198
1263
  `vault: ${target}`,
1199
1264
  `domain: ${domain}`,
@@ -1201,6 +1266,7 @@ async function runInit(input) {
1201
1266
  `created: ${created.length}, preserved: ${preserved.length}`,
1202
1267
  `discovered tags: ${discovered_tags}`,
1203
1268
  skipEnv ? "env: skipped" : `env: ${envWritten}`,
1269
+ input.noGitignore ? "gitignore: skipped" : `gitignore: ${gitignoreWritten ? "written" : "unchanged"}`,
1204
1270
  `web clipper template: ${WEB_CLIPPER_TEMPLATE_REL}`,
1205
1271
  "web clipper import: open Obsidian Web Clipper Settings and import the JSON in each browser profile"
1206
1272
  ].join("\n");
@@ -1230,7 +1296,9 @@ async function runInit(input) {
1230
1296
  web_clipper_template_path: WEB_CLIPPER_TEMPLATE_REL,
1231
1297
  web_clipper_readme_path: WEB_CLIPPER_README_REL,
1232
1298
  web_clipper_template_created: created.includes(WEB_CLIPPER_TEMPLATE_REL),
1233
- web_clipper_template_preserved: preserved.includes(WEB_CLIPPER_TEMPLATE_REL)
1299
+ web_clipper_template_preserved: preserved.includes(WEB_CLIPPER_TEMPLATE_REL),
1300
+ gitignore_written: gitignoreWritten,
1301
+ gitignore_preserved: gitignorePreserved
1234
1302
  })
1235
1303
  };
1236
1304
  }
@@ -3147,13 +3215,7 @@ function runVaultSyncHealth(home, syncMode, env = process.env) {
3147
3215
  checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
3148
3216
  } else {
3149
3217
  const content = readFileSync10(filterPath, "utf8");
3150
- const missing = [
3151
- "remotely-save/data.json",
3152
- ".skillwiki/sync.lock",
3153
- ".skillwiki/memory/",
3154
- ".skillwiki/memory-topics.json",
3155
- ".claude/settings.local.json"
3156
- ].filter((item) => !content.includes(item));
3218
+ const missing = VAULT_SYNC_FILTER_REQUIRED_EXCLUDES.filter((item) => !content.includes(item));
3157
3219
  checks.push(missing.length > 0 ? { id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "warn", detail: `Missing excludes: ${missing.join(", ")}` } : { id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "pass", detail: "Required excludes present" });
3158
3220
  }
3159
3221
  checks.push({ id: "vault_sync_snapshot_guard", label: "Snapshot script guard", status: "pass", detail: "Not a snapshotter host \u2014 check skipped" });
@@ -10108,7 +10170,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
10108
10170
  if (dirty) {
10109
10171
  return emit(dirty, void 0, { postCommit: false });
10110
10172
  }
10111
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-SP2A57YY.js");
10173
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-DOOM7XIV.js");
10112
10174
  const run = await runManagedWriteTransaction2({
10113
10175
  vault,
10114
10176
  command,
@@ -10397,7 +10459,7 @@ program.command("lang").description("get or set the vault language").option("--l
10397
10459
  explain: !!opts.explain
10398
10460
  }));
10399
10461
  });
10400
- program.command("init").description("bootstrap a new vault with SCHEMA.md, index.md, log.md").option("--target <dir>", "explicit target directory").requiredOption("--domain <text>", "knowledge domain seed").option("--taxonomy <csv>", "comma-separated tag list").option("--lang <code>", "output language (BCP 47 or alias)").option("--force", "override existing target / env conflict", false).option("--no-env", "skip writing ~/.skillwiki/.env").option("--profile <name>", "write as named wiki profile instead of WIKI_PATH").action(async (opts) => {
10462
+ program.command("init").description("bootstrap a new vault with SCHEMA.md, index.md, log.md").option("--target <dir>", "explicit target directory").requiredOption("--domain <text>", "knowledge domain seed").option("--taxonomy <csv>", "comma-separated tag list").option("--lang <code>", "output language (BCP 47 or alias)").option("--force", "override existing target / env conflict", false).option("--no-env", "skip writing ~/.skillwiki/.env").option("--profile <name>", "write as named wiki profile instead of WIKI_PATH").option("--no-gitignore", "skip writing the vault .gitignore hygiene template").option("--write-gitignore", "merge hygiene .gitignore only (existing vault; does not rewrite SCHEMA.md)", false).action(async (opts) => {
10401
10463
  const templates = new URL("../templates/", import.meta.url).pathname;
10402
10464
  const taxonomy = typeof opts.taxonomy === "string" ? opts.taxonomy.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
10403
10465
  emit(await runInit({
@@ -10410,7 +10472,9 @@ program.command("init").description("bootstrap a new vault with SCHEMA.md, index
10410
10472
  lang: opts.lang,
10411
10473
  force: !!opts.force,
10412
10474
  noEnv: opts.env === false,
10413
- profile: opts.profile
10475
+ profile: opts.profile,
10476
+ noGitignore: opts.gitignore === false,
10477
+ writeGitignoreOnly: !!opts.writeGitignore
10414
10478
  }));
10415
10479
  });
10416
10480
  async function resolveVaultArg(arg, wiki) {
@@ -6,7 +6,7 @@ import {
6
6
  runManagedWritePeerGate,
7
7
  runManagedWritePreflight,
8
8
  runManagedWriteTransaction
9
- } from "./chunk-FM6RWVW6.js";
9
+ } from "./chunk-LXZPRAZU.js";
10
10
  import "./chunk-NG72ZD4C.js";
11
11
  import "./chunk-6AMXNODT.js";
12
12
  import "./chunk-MHGUYPGE.js";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-AVVFFPQ5.js";
4
+ } from "./chunk-Y2EPEJT7.js";
5
5
  import "./chunk-7I2TPIV5.js";
6
6
  import "./chunk-NHRRYAXT.js";
7
- import "./chunk-FM6RWVW6.js";
7
+ import "./chunk-LXZPRAZU.js";
8
8
  import "./chunk-NG72ZD4C.js";
9
9
  import "./chunk-JMV7YBQN.js";
10
10
  import "./chunk-6AMXNODT.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.56",
3
+ "version": "0.10.58",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.56",
3
+ "version": "0.10.58",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 20 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.56",
3
+ "version": "0.10.58",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 20 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.56",
3
+ "version": "0.10.58",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -287,6 +287,7 @@ skillwiki has multiple distribution channels that can drift:
287
287
  | Claude plugin | `~/.claude/plugins/cache/llm-wiki/` | `claude plugin update skillwiki@llm-wiki` |
288
288
  | Codex plugin | `~/.codex/plugins/cache/llm-wiki/` | `codex plugin marketplace upgrade llm-wiki`, then reinstall or restart Codex as needed |
289
289
  | Grok plugin | `~/.grok/installed-plugins/` (marketplace cache under `~/.grok/marketplace-cache/`) | `grok plugin update skillwiki`, then start a new session or reload plugins |
290
+ | Cursor / Grok Bot (Team GitHub import) | `~/.cursor/plugins/cache/llm-wiki/` and `~/.cursor/plugins/marketplaces/github.com/karlorz/llm-wiki/<sha>/` | Cursor Dashboard → Plugins → **Refresh** or Enable Auto Refresh on `karlorz/llm-wiki`. Reinstall does not move a pinned snapshot. |
290
291
  | Local git dev | source repo checkout | `npm link ./packages/cli` (from repo root) |
291
292
  **Check versions:** `skillwiki doctor` reports Plugin/CLI version mismatch warnings when installed channels disagree. For Grok, also inspect `~/.grok/installed-plugins/*/.claude-plugin/plugin.json` version and agent frontmatter under `agents/*.md`.
292
293
  **Plugin channel rule:** Plugin-managed skills and agents are not refreshed with `skillwiki install`. When Claude, Codex, or Grok plugin is installed and enabled, the plugin install root is the skill/agent provider; `skillwiki install` is only a legacy/standalone copier for `~/.claude/skills/`.
@@ -22,6 +22,11 @@ None for the first run.
22
22
  3. Propose a 10–15 tag taxonomy tailored to the domain. Confirm or accept the user's revision.
23
23
  4. Ask the language question: "What language should generated page prose use? Default is `en`. Aliases like `chinese-traditional` or `zh-Hant` are accepted."
24
24
  5. Run `skillwiki init --target <dir> --domain "<answer>" --taxonomy "<comma list>" --lang "<lang>"`.
25
+ Init always writes or merges a vault `.gitignore` for GitHub-sync hygiene
26
+ (work-complete journals, last-op, locks, memory/graph caches). It does **not**
27
+ ignore `session-brief.*` or `agent-memory-trends/`. Pass `--no-gitignore` only
28
+ when the user explicitly does not want that file. For an existing vault:
29
+ `skillwiki init --target <dir> --domain existing --write-gitignore`.
25
30
  6. Report the installed Web Clipper assets at
26
31
  `_Templates/web-clipper/llm-wiki-clippings.json` and
27
32
  `_Templates/web-clipper/readme.txt`. Tell the user to open Obsidian Web
@@ -197,6 +197,7 @@ High-signal safety rule:
197
197
  - Do **not** run `git reset --hard`, direct commits, or manual snapshot scripts to "fix" divergence.
198
198
  - Promotion is owned by `wiki-snapshot.timer` by default. Publishers never start systemd units.
199
199
  - `skillwiki work-complete` may finish with `committed=false` on sg01; later snapshot promotion owns the Git commit/push.
200
+ - `.skillwiki/work-complete/*.env` journals are local retry hygiene, not GitHub SSOT. Do not treat them as publishable. Prefer last-op pathspecs over a raw `git add -A` when staging a managed completion. Vault `.gitignore` and rclone push filters must exclude `work-complete/` and `last-op.json`.
200
201
 
201
202
  ### Authorized Git leaf rules
202
203
 
@@ -287,6 +287,7 @@ skillwiki has multiple distribution channels that can drift:
287
287
  | Claude plugin | `~/.claude/plugins/cache/llm-wiki/` | `claude plugin update skillwiki@llm-wiki` |
288
288
  | Codex plugin | `~/.codex/plugins/cache/llm-wiki/` | `codex plugin marketplace upgrade llm-wiki`, then reinstall or restart Codex as needed |
289
289
  | Grok plugin | `~/.grok/installed-plugins/` (marketplace cache under `~/.grok/marketplace-cache/`) | `grok plugin update skillwiki`, then start a new session or reload plugins |
290
+ | Cursor / Grok Bot (Team GitHub import) | `~/.cursor/plugins/cache/llm-wiki/` and `~/.cursor/plugins/marketplaces/github.com/karlorz/llm-wiki/<sha>/` | Cursor Dashboard → Plugins → **Refresh** or Enable Auto Refresh on `karlorz/llm-wiki`. Reinstall does not move a pinned snapshot. |
290
291
  | Local git dev | source repo checkout | `npm link ./packages/cli` (from repo root) |
291
292
  **Check versions:** `skillwiki doctor` reports Plugin/CLI version mismatch warnings when installed channels disagree. For Grok, also inspect `~/.grok/installed-plugins/*/.claude-plugin/plugin.json` version and agent frontmatter under `agents/*.md`.
292
293
  **Plugin channel rule:** Plugin-managed skills and agents are not refreshed with `skillwiki install`. When Claude, Codex, or Grok plugin is installed and enabled, the plugin install root is the skill/agent provider; `skillwiki install` is only a legacy/standalone copier for `~/.claude/skills/`.
@@ -22,6 +22,11 @@ None for the first run.
22
22
  3. Propose a 10–15 tag taxonomy tailored to the domain. Confirm or accept the user's revision.
23
23
  4. Ask the language question: "What language should generated page prose use? Default is `en`. Aliases like `chinese-traditional` or `zh-Hant` are accepted."
24
24
  5. Run `skillwiki init --target <dir> --domain "<answer>" --taxonomy "<comma list>" --lang "<lang>"`.
25
+ Init always writes or merges a vault `.gitignore` for GitHub-sync hygiene
26
+ (work-complete journals, last-op, locks, memory/graph caches). It does **not**
27
+ ignore `session-brief.*` or `agent-memory-trends/`. Pass `--no-gitignore` only
28
+ when the user explicitly does not want that file. For an existing vault:
29
+ `skillwiki init --target <dir> --domain existing --write-gitignore`.
25
30
  6. Report the installed Web Clipper assets at
26
31
  `_Templates/web-clipper/llm-wiki-clippings.json` and
27
32
  `_Templates/web-clipper/readme.txt`. Tell the user to open Obsidian Web
@@ -197,6 +197,7 @@ High-signal safety rule:
197
197
  - Do **not** run `git reset --hard`, direct commits, or manual snapshot scripts to "fix" divergence.
198
198
  - Promotion is owned by `wiki-snapshot.timer` by default. Publishers never start systemd units.
199
199
  - `skillwiki work-complete` may finish with `committed=false` on sg01; later snapshot promotion owns the Git commit/push.
200
+ - `.skillwiki/work-complete/*.env` journals are local retry hygiene, not GitHub SSOT. Do not treat them as publishable. Prefer last-op pathspecs over a raw `git add -A` when staging a managed completion. Vault `.gitignore` and rclone push filters must exclude `work-complete/` and `last-op.json`.
200
201
 
201
202
  ### Authorized Git leaf rules
202
203