skillwiki 0.10.9 → 0.10.11

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.
@@ -22,6 +22,7 @@ import {
22
22
  } from "./chunk-U34B2XQJ.js";
23
23
  import {
24
24
  CONFIG_KEYS,
25
+ git,
25
26
  isValidWikiProfileKey,
26
27
  listReviewRequiredOps,
27
28
  loadFleetManifestAndHost,
@@ -3212,12 +3213,13 @@ function buildCliSurface() {
3212
3213
  program.command("sync");
3213
3214
  program.command("backup");
3214
3215
  program.command("seed").option("--wiki <name>");
3215
- program.command("observe").requiredOption("--text <text>").option("--kind <kind>").option("--project <slug>").option("--wiki <name>");
3216
+ program.command("observe").requiredOption("--text <text>").option("--kind <kind>").option("--project <slug>").option("--severity <level>").option("--capture-budget <n>").option("--wiki <name>");
3216
3217
  program.command("session-brief").option("--project <slug>").option("--write").option("--wiki <name>");
3217
3218
  program.command("memory");
3218
3219
  program.command("ingest").requiredOption("--vault <path>").requiredOption("--type <type>").requiredOption("--title <title>").option("--tags <csv>").option("--provenance <provenance>").option("--dry-run");
3219
3220
  program.command("fleet");
3220
3221
  program.command("page");
3222
+ program.command("write-preflight").option("--command <name>").option("--dirty-threshold <n>").option("--skip-dirty").option("--prior-artifact-file <path>").option("--prior-artifact-text <text>").option("--consecutive-no-decision <n>").option("--no-decision-threshold <n>").option("--human-allow").option("--mission-kind <kind>").option("--skip-mission").option("--project <slug>").option("--capture-day <date>").option("--capture-budget <n>").option("--severity <level>").option("--skip-budget").option("--checks <list>").option("--wiki <name>");
3221
3223
  const graphCmd = program.commands.find((c) => c.name() === "graph");
3222
3224
  graphCmd.command("build").option("--out <path>").option("--wiki <name>");
3223
3225
  const canvasCmd = program.commands.find((c) => c.name() === "canvas");
@@ -4650,7 +4652,7 @@ async function runSyncLintDelta(input) {
4650
4652
  const { mkdtempSync, rmSync, existsSync: fsExists } = await import("fs");
4651
4653
  const { join: pathJoin } = await import("path");
4652
4654
  const { tmpdir } = await import("os");
4653
- const { execFileSync: execFileSync2 } = await import("child_process");
4655
+ const { execFileSync: execFileSync3 } = await import("child_process");
4654
4656
  const vault = input.vault;
4655
4657
  const baseRef = input.baseRef ?? "origin/main";
4656
4658
  const days = input.days ?? 90;
@@ -4663,7 +4665,7 @@ async function runSyncLintDelta(input) {
4663
4665
  };
4664
4666
  }
4665
4667
  try {
4666
- execFileSync2("git", ["rev-parse", "--verify", baseRef], {
4668
+ execFileSync3("git", ["rev-parse", "--verify", baseRef], {
4667
4669
  cwd: vault,
4668
4670
  stdio: ["pipe", "pipe", "pipe"]
4669
4671
  });
@@ -4693,12 +4695,12 @@ async function runSyncLintDelta(input) {
4693
4695
  const fullFps = collectLintErrorFingerprints(fullOutput);
4694
4696
  const tmpRoot = mkdtempSync(pathJoin(tmpdir(), "skillwiki-lint-delta-"));
4695
4697
  try {
4696
- const archive = execFileSync2("git", ["archive", "--format=tar", baseRef], {
4698
+ const archive = execFileSync3("git", ["archive", "--format=tar", baseRef], {
4697
4699
  cwd: vault,
4698
4700
  stdio: ["pipe", "pipe", "pipe"],
4699
4701
  maxBuffer: 256 * 1024 * 1024
4700
4702
  });
4701
- execFileSync2("tar", ["-xf", "-"], {
4703
+ execFileSync3("tar", ["-xf", "-"], {
4702
4704
  cwd: tmpRoot,
4703
4705
  input: archive,
4704
4706
  stdio: ["pipe", "pipe", "pipe"]
@@ -7124,10 +7126,401 @@ function readCliPackageJson(baseUrl = import.meta.url) {
7124
7126
  throw new Error(`Could not locate skillwiki package.json from ${baseUrl}`);
7125
7127
  }
7126
7128
 
7129
+ // src/utils/vault-write-gates.ts
7130
+ import { execFileSync as execFileSync2 } from "child_process";
7131
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
7132
+ import { join as join26, relative as relative3 } from "path";
7133
+ var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
7134
+ var DEFAULT_CAPTURE_BUDGET = 20;
7135
+ var DEFAULT_NO_DECISION_STREAK = 3;
7136
+ var GateError = {
7137
+ VAULT_DIRTY_BACKLOG: "VAULT_DIRTY_BACKLOG",
7138
+ DIMINISHING_RETURNS: "DIMINISHING_RETURNS",
7139
+ CAPTURE_BUDGET_EXHAUSTED: "CAPTURE_BUDGET_EXHAUSTED",
7140
+ USAGE: "USAGE",
7141
+ VAULT_PATH_INVALID: "VAULT_PATH_INVALID"
7142
+ };
7143
+ var HYGIENE_COMMANDS = /* @__PURE__ */ new Set([
7144
+ "write-preflight",
7145
+ "sync status",
7146
+ "sync push",
7147
+ "sync pull",
7148
+ "sync lock",
7149
+ "sync unlock",
7150
+ "sync resolve-derived",
7151
+ "sync journal list",
7152
+ "sync journal clear-stale",
7153
+ "sync lint-delta",
7154
+ "sync peers",
7155
+ "work-complete",
7156
+ "work-validate",
7157
+ "log materialize",
7158
+ "log migrate-legacy",
7159
+ "index rebuild",
7160
+ "projections materialize",
7161
+ "doctor",
7162
+ "health",
7163
+ "lint",
7164
+ "status",
7165
+ "path",
7166
+ "fleet context",
7167
+ "fleet validate",
7168
+ "fleet health"
7169
+ ]);
7170
+ function isHygieneCommand(command) {
7171
+ const c = command.trim().toLowerCase().replace(/\s+/g, " ");
7172
+ if (HYGIENE_COMMANDS.has(c)) return true;
7173
+ if (c === "sync" || c.startsWith("sync ")) {
7174
+ const rest = c.slice(5).trim();
7175
+ if (!rest) return true;
7176
+ return HYGIENE_COMMANDS.has(`sync ${rest}`);
7177
+ }
7178
+ return false;
7179
+ }
7180
+ function measureDirtyVolume(vault) {
7181
+ const empty = (extra = {}) => ({
7182
+ porcelain_lines: 0,
7183
+ expanded_files: 0,
7184
+ modified: 0,
7185
+ untracked: 0,
7186
+ buckets: [],
7187
+ threshold: 0,
7188
+ over_threshold: false,
7189
+ is_git_repo: false,
7190
+ ...extra
7191
+ });
7192
+ if (!existsSync14(vault) || !statSync3(vault).isDirectory()) {
7193
+ return empty();
7194
+ }
7195
+ const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
7196
+ if (!gitDir) {
7197
+ return empty({ is_git_repo: false });
7198
+ }
7199
+ let porcelain = "";
7200
+ try {
7201
+ porcelain = execFileSync2("git", ["status", "--porcelain"], {
7202
+ cwd: vault,
7203
+ encoding: "utf8",
7204
+ stdio: ["pipe", "pipe", "pipe"]
7205
+ });
7206
+ } catch {
7207
+ porcelain = "";
7208
+ }
7209
+ const lines = porcelain.replace(/\n+$/, "").split("\n").filter((l) => l.length > 0);
7210
+ let modified = 0;
7211
+ let untracked = 0;
7212
+ let expanded = 0;
7213
+ const bucketMap = /* @__PURE__ */ new Map();
7214
+ const addBucket = (rel, n) => {
7215
+ const top = rel.split(/[/\\]/)[0] || ".";
7216
+ bucketMap.set(top, (bucketMap.get(top) ?? 0) + n);
7217
+ };
7218
+ for (const line of lines) {
7219
+ const match = line.match(/^(.{2}) (.*)$/);
7220
+ if (!match) continue;
7221
+ const code = match[1];
7222
+ let pathPart = match[2] ?? "";
7223
+ if (pathPart.includes(" -> ")) {
7224
+ pathPart = pathPart.split(" -> ").pop() ?? pathPart;
7225
+ }
7226
+ const rel = pathPart.replace(/^"(.*)"$/, "$1").trim();
7227
+ if (!rel) continue;
7228
+ if (code === "??") {
7229
+ untracked += 1;
7230
+ const abs = join26(vault, rel);
7231
+ if (existsSync14(abs) && statSync3(abs).isDirectory()) {
7232
+ const files = listFilesRecursive(abs);
7233
+ expanded += files.length;
7234
+ addBucket(rel, files.length);
7235
+ } else {
7236
+ expanded += 1;
7237
+ addBucket(rel, 1);
7238
+ }
7239
+ } else {
7240
+ modified += 1;
7241
+ expanded += 1;
7242
+ addBucket(rel, 1);
7243
+ }
7244
+ }
7245
+ const buckets = [...bucketMap.entries()].map(([bucket, count]) => ({ bucket, count })).sort((a, b) => b.count - a.count);
7246
+ return {
7247
+ porcelain_lines: lines.length,
7248
+ expanded_files: expanded,
7249
+ modified,
7250
+ untracked,
7251
+ buckets,
7252
+ threshold: 0,
7253
+ over_threshold: false,
7254
+ is_git_repo: true
7255
+ };
7256
+ }
7257
+ function listFilesRecursive(dir) {
7258
+ const out = [];
7259
+ let entries;
7260
+ try {
7261
+ entries = readdirSync4(dir);
7262
+ } catch {
7263
+ return out;
7264
+ }
7265
+ for (const name of entries) {
7266
+ if (name === ".git") continue;
7267
+ const p = join26(dir, name);
7268
+ try {
7269
+ const st = statSync3(p);
7270
+ if (st.isDirectory()) out.push(...listFilesRecursive(p));
7271
+ else if (st.isFile()) out.push(p);
7272
+ } catch {
7273
+ }
7274
+ }
7275
+ return out;
7276
+ }
7277
+ function evaluateDirtyVolumeGate(input) {
7278
+ const threshold = input.threshold ?? DEFAULT_DIRTY_VOLUME_THRESHOLD;
7279
+ const report = measureDirtyVolume(input.vault);
7280
+ report.threshold = threshold;
7281
+ report.over_threshold = report.is_git_repo && report.expanded_files > threshold;
7282
+ if (input.skip) {
7283
+ return { allowed: true, reason: "skipped", report };
7284
+ }
7285
+ if (input.command && isHygieneCommand(input.command)) {
7286
+ return { allowed: true, reason: "hygiene", report };
7287
+ }
7288
+ if (!report.is_git_repo) {
7289
+ return { allowed: true, reason: "not_a_git_repo", report };
7290
+ }
7291
+ if (!report.over_threshold) {
7292
+ return { allowed: true, reason: "under_threshold", report };
7293
+ }
7294
+ const top = report.buckets.slice(0, 5).map((b) => `${b.bucket}:${b.count}`).join(", ");
7295
+ return {
7296
+ allowed: false,
7297
+ reason: "over_threshold",
7298
+ code: GateError.VAULT_DIRTY_BACKLOG,
7299
+ report,
7300
+ humanHint: `Vault dirty volume ${report.expanded_files} exceeds threshold ${threshold} (porcelain ${report.porcelain_lines}; buckets: ${top || "none"}). Triage/commit keep-set before more non-hygiene writes. Hygiene commands still allowed.`
7301
+ };
7302
+ }
7303
+ var SATURATION_PATTERNS = [
7304
+ /\benablement\s+\*\*saturated\*\*/i,
7305
+ /\bsaturated\b/i,
7306
+ /\bexplicit hold\b/i,
7307
+ /\bno further\b.*\bbatches\b/i,
7308
+ /\bpause (?:the )?job\b/i,
7309
+ /\bcancel research\b/i,
7310
+ /\bkill research\b/i,
7311
+ /\bdiminishing returns\b/i,
7312
+ /\bhuman send only\b/i,
7313
+ /\bno new decision\b/i,
7314
+ /\bHOLD:\b/,
7315
+ /\bstatus:\s*saturated\b/i,
7316
+ /\bmission:\s*cancelled\b/i,
7317
+ /\bcancelled mission\b/i
7318
+ ];
7319
+ function detectSaturationSignals(text) {
7320
+ if (!text) return [];
7321
+ const hits = [];
7322
+ for (const re of SATURATION_PATTERNS) {
7323
+ const m = text.match(re);
7324
+ if (m) hits.push(m[0]);
7325
+ }
7326
+ return hits;
7327
+ }
7328
+ function evaluateMissionCycleGate(input) {
7329
+ if (input.humanAllow) {
7330
+ return { allowed: true, reason: "human_allow", signals: [] };
7331
+ }
7332
+ const signals = detectSaturationSignals(input.priorArtifactText ?? "");
7333
+ if (signals.length > 0) {
7334
+ return {
7335
+ allowed: false,
7336
+ reason: "saturated_text",
7337
+ code: GateError.DIMINISHING_RETURNS,
7338
+ signals,
7339
+ humanHint: `Mission saturated/hold/stop signal(s) in prior artifact: ${signals.slice(0, 5).join("; ")}. Refuse new ${input.missionKind ?? "cycle"} artifact without --human-allow.`
7340
+ };
7341
+ }
7342
+ const streak = input.consecutiveNoNewDecision ?? 0;
7343
+ const thresh = input.noDecisionThreshold ?? DEFAULT_NO_DECISION_STREAK;
7344
+ if (streak >= thresh) {
7345
+ return {
7346
+ allowed: false,
7347
+ reason: "no_decision_streak",
7348
+ code: GateError.DIMINISHING_RETURNS,
7349
+ signals: [`no_new_decision_streak=${streak}`],
7350
+ humanHint: `${streak} consecutive no-new-decision cycles (\u2265 ${thresh}). Refuse further cycle artifacts without --human-allow.`
7351
+ };
7352
+ }
7353
+ return { allowed: true, reason: "clean", signals: [] };
7354
+ }
7355
+ function utcDay(d = /* @__PURE__ */ new Date()) {
7356
+ return d.toISOString().slice(0, 10);
7357
+ }
7358
+ var CAPTURE_HYGIENE_CONTRACT = `After each productive cycle (or when daily capture budget is reached), stage/commit the keep-set or discard noise so untracked investigate files for the project stay within the daily budget (default ${DEFAULT_CAPTURE_BUDGET}). P0 severity escapes the budget. Do not silently grow unbounded untracked investigate files.`;
7359
+ function listProjectDayCaptures(vault, project, day) {
7360
+ const found = [];
7361
+ const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
7362
+ if (!slug || !existsSync14(vault)) return found;
7363
+ const consider = (abs, rel) => {
7364
+ if (!rel.endsWith(".md")) return;
7365
+ const base = rel.split(/[/\\]/).pop() ?? "";
7366
+ if (!base.startsWith(day)) return;
7367
+ const normRel = rel.replace(/\\/g, "/");
7368
+ const captureLike = /investigate|dev-loop|research-cycle|pilot-q|cycle-\d+|batch-|idle-no-new|observation/.test(normRel) || /(^|\/)raw\/transcripts\//.test(normRel) || /(^|\/)requirements\//.test(normRel);
7369
+ if (!captureLike && !/transcripts/.test(normRel)) {
7370
+ return;
7371
+ }
7372
+ const norm = rel.replace(/\\/g, "/");
7373
+ if (norm.startsWith(`projects/${slug}/`)) {
7374
+ found.push(norm);
7375
+ return;
7376
+ }
7377
+ if (norm.startsWith("raw/transcripts/")) {
7378
+ try {
7379
+ const body = readFileSync12(abs, "utf8");
7380
+ if (body.includes(`project: ${slug}`) || body.includes(`project: "[[${slug}]]"`) || body.includes(`project: [[${slug}]]`)) {
7381
+ found.push(norm);
7382
+ } else if (base.includes(slug)) {
7383
+ found.push(norm);
7384
+ }
7385
+ } catch {
7386
+ }
7387
+ }
7388
+ };
7389
+ const walk = (dir, relBase) => {
7390
+ if (!existsSync14(dir)) return;
7391
+ let entries;
7392
+ try {
7393
+ entries = readdirSync4(dir);
7394
+ } catch {
7395
+ return;
7396
+ }
7397
+ for (const name of entries) {
7398
+ const abs = join26(dir, name);
7399
+ const rel = relBase ? `${relBase}/${name}` : name;
7400
+ try {
7401
+ const st = statSync3(abs);
7402
+ if (st.isDirectory()) walk(abs, rel);
7403
+ else if (st.isFile()) consider(abs, rel);
7404
+ } catch {
7405
+ }
7406
+ }
7407
+ };
7408
+ walk(join26(vault, "raw", "transcripts"), "raw/transcripts");
7409
+ walk(join26(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
7410
+ walk(join26(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
7411
+ const workRoot = join26(vault, "projects", slug, "work");
7412
+ if (existsSync14(workRoot)) {
7413
+ try {
7414
+ for (const name of readdirSync4(workRoot)) {
7415
+ if (!name.startsWith(day)) continue;
7416
+ if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
7417
+ const abs = join26(workRoot, name);
7418
+ if (statSync3(abs).isDirectory()) {
7419
+ for (const f of listFilesRecursive(abs)) {
7420
+ const rel = relative3(vault, f).replace(/\\/g, "/");
7421
+ if (rel.endsWith(".md")) found.push(rel);
7422
+ }
7423
+ }
7424
+ }
7425
+ } catch {
7426
+ }
7427
+ }
7428
+ return [...new Set(found)].sort();
7429
+ }
7430
+ function evaluateCaptureBudget(input) {
7431
+ const day = input.day ?? utcDay();
7432
+ const budget = input.budget ?? DEFAULT_CAPTURE_BUDGET;
7433
+ const paths = listProjectDayCaptures(input.vault, input.project, day);
7434
+ const used = paths.length;
7435
+ const report = {
7436
+ project: input.project,
7437
+ day,
7438
+ budget,
7439
+ used,
7440
+ remaining: Math.max(0, budget - used),
7441
+ paths
7442
+ };
7443
+ if (input.skip) {
7444
+ return { allowed: true, reason: "skipped", report };
7445
+ }
7446
+ const sev = (input.severity ?? "").toString().trim().toUpperCase();
7447
+ if (sev === "P0" || sev === "0") {
7448
+ return { allowed: true, reason: "p0_escape", report };
7449
+ }
7450
+ if (used >= budget) {
7451
+ return {
7452
+ allowed: false,
7453
+ reason: "exhausted",
7454
+ code: GateError.CAPTURE_BUDGET_EXHAUSTED,
7455
+ report,
7456
+ humanHint: `Capture budget exhausted for project ${input.project} on ${day}: ${used}/${budget}. ${CAPTURE_HYGIENE_CONTRACT}`
7457
+ };
7458
+ }
7459
+ return { allowed: true, reason: "under_budget", report };
7460
+ }
7461
+ function runWritePreflight(input) {
7462
+ if (!input.vault || !existsSync14(input.vault)) {
7463
+ return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
7464
+ }
7465
+ const want = new Set(input.checks ?? ["all"]);
7466
+ const runAll = want.has("all");
7467
+ const checks = {};
7468
+ const refused = [];
7469
+ if (runAll || want.has("dirty")) {
7470
+ const dirty = evaluateDirtyVolumeGate({
7471
+ vault: input.vault,
7472
+ threshold: input.dirtyThreshold,
7473
+ skip: input.skipDirty,
7474
+ command: input.command
7475
+ });
7476
+ checks.dirty = dirty;
7477
+ if (!dirty.allowed) {
7478
+ refused.push({ code: dirty.code, humanHint: dirty.humanHint });
7479
+ }
7480
+ }
7481
+ const missionRequested = runAll || want.has("mission") || input.priorArtifactText != null || input.consecutiveNoNewDecision != null;
7482
+ if (missionRequested && !input.skipMission) {
7483
+ if (want.has("mission") || input.priorArtifactText != null && input.priorArtifactText.length > 0 || input.consecutiveNoNewDecision != null && input.consecutiveNoNewDecision > 0) {
7484
+ const mission = evaluateMissionCycleGate({
7485
+ priorArtifactText: input.priorArtifactText,
7486
+ consecutiveNoNewDecision: input.consecutiveNoNewDecision,
7487
+ noDecisionThreshold: input.noDecisionThreshold,
7488
+ humanAllow: input.humanAllow,
7489
+ missionKind: input.missionKind
7490
+ });
7491
+ checks.mission = mission;
7492
+ if (!mission.allowed) {
7493
+ refused.push({ code: mission.code, humanHint: mission.humanHint });
7494
+ }
7495
+ }
7496
+ }
7497
+ if ((runAll || want.has("budget")) && input.project && !input.skipBudget) {
7498
+ const budget = evaluateCaptureBudget({
7499
+ vault: input.vault,
7500
+ project: input.project,
7501
+ day: input.captureDay,
7502
+ budget: input.captureBudget,
7503
+ severity: input.severity
7504
+ });
7505
+ checks.budget = budget;
7506
+ if (!budget.allowed) {
7507
+ refused.push({ code: budget.code, humanHint: budget.humanHint });
7508
+ }
7509
+ }
7510
+ const allowed = refused.length === 0;
7511
+ return ok({
7512
+ allowed,
7513
+ checks,
7514
+ refused,
7515
+ hygiene_contract: CAPTURE_HYGIENE_CONTRACT,
7516
+ humanHint: allowed ? "write preflight: allowed" : `write preflight: refused \u2014 ${refused.map((r) => r.code).join(", ")}`
7517
+ });
7518
+ }
7519
+
7127
7520
  // src/commands/observe.ts
7128
7521
  import { mkdir as mkdir6, writeFile as writeFile4 } from "fs/promises";
7129
- import { existsSync as existsSync14, statSync as statSync3 } from "fs";
7130
- import { join as join26 } from "path";
7522
+ import { existsSync as existsSync15, statSync as statSync4 } from "fs";
7523
+ import { join as join27 } from "path";
7131
7524
  import { createHash as createHash6 } from "crypto";
7132
7525
  var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
7133
7526
  function slugify(text) {
@@ -7150,13 +7543,33 @@ async function runObserve(input) {
7150
7543
  result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
7151
7544
  };
7152
7545
  }
7153
- if (!existsSync14(input.vault) || !statSync3(input.vault).isDirectory()) {
7546
+ if (!existsSync15(input.vault) || !statSync4(input.vault).isDirectory()) {
7154
7547
  return {
7155
7548
  exitCode: ExitCode.VAULT_PATH_INVALID,
7156
7549
  result: err("VAULT_PATH_INVALID", { path: input.vault })
7157
7550
  };
7158
7551
  }
7159
- const transcriptsDir = join26(input.vault, "raw", "transcripts");
7552
+ if (input.project && !input.skipCaptureBudget) {
7553
+ const budget = evaluateCaptureBudget({
7554
+ vault: input.vault,
7555
+ project: input.project,
7556
+ day: input.captureDay,
7557
+ budget: input.captureBudget,
7558
+ severity: input.severity
7559
+ });
7560
+ if (!budget.allowed) {
7561
+ return {
7562
+ exitCode: ExitCode.PREFLIGHT_FAILED,
7563
+ result: err(GateError.CAPTURE_BUDGET_EXHAUSTED, {
7564
+ reason: budget.reason,
7565
+ report: budget.report,
7566
+ humanHint: budget.humanHint,
7567
+ hygiene_contract: CAPTURE_HYGIENE_CONTRACT
7568
+ })
7569
+ };
7570
+ }
7571
+ }
7572
+ const transcriptsDir = join27(input.vault, "raw", "transcripts");
7160
7573
  try {
7161
7574
  await mkdir6(transcriptsDir, { recursive: true });
7162
7575
  } catch {
@@ -7168,7 +7581,7 @@ async function runObserve(input) {
7168
7581
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7169
7582
  const slug = slugify(input.text);
7170
7583
  const fileName = `${today}-observation-${slug}.md`;
7171
- const filePath = join26(transcriptsDir, fileName);
7584
+ const filePath = join27(transcriptsDir, fileName);
7172
7585
  const body = `
7173
7586
  ${input.text.trim()}
7174
7587
  `;
@@ -7210,7 +7623,7 @@ ${input.text.trim()}
7210
7623
  // src/commands/memory.ts
7211
7624
  import { createHash as createHash7 } from "crypto";
7212
7625
  import { mkdir as mkdir7, readFile as readFile17, readdir as readdir5, stat as stat5, writeFile as writeFile5 } from "fs/promises";
7213
- import { basename as basename2, extname, join as join27, relative as relative3, sep as sep3 } from "path";
7626
+ import { basename as basename2, extname, join as join28, relative as relative4, sep as sep3 } from "path";
7214
7627
 
7215
7628
  // src/utils/memory-authority.ts
7216
7629
  var TIER_RANK = {
@@ -7329,8 +7742,8 @@ async function runMemoryIndex(input) {
7329
7742
  }
7330
7743
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
7331
7744
  const relCachePath = memoryCacheRelPath(input.project);
7332
- const absCachePath = join27(input.vault, relCachePath);
7333
- await mkdir7(join27(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7745
+ const absCachePath = join28(input.vault, relCachePath);
7746
+ await mkdir7(join28(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
7334
7747
  await writeFile5(absCachePath, `${JSON.stringify({
7335
7748
  generated_at: generatedAt,
7336
7749
  project: input.project,
@@ -7522,7 +7935,7 @@ async function buildMemoryIndexState(pages, project) {
7522
7935
  }
7523
7936
  async function checkMemoryIndex(vault, project, current) {
7524
7937
  const relCachePath = memoryCacheRelPath(project);
7525
- const cacheText = await readIfExists2(join27(vault, relCachePath));
7938
+ const cacheText = await readIfExists2(join28(vault, relCachePath));
7526
7939
  if (!cacheText) {
7527
7940
  return {
7528
7941
  ok: true,
@@ -7931,7 +8344,7 @@ async function walkImportFiles(dir, out) {
7931
8344
  const entries = await readdir5(dir, { withFileTypes: true });
7932
8345
  for (const entry of entries) {
7933
8346
  if (entry.name === ".git" || entry.name === "node_modules") continue;
7934
- const path = join27(dir, entry.name);
8347
+ const path = join28(dir, entry.name);
7935
8348
  if (entry.isDirectory()) {
7936
8349
  await walkImportFiles(path, out);
7937
8350
  } else if (entry.isFile() && isImportCandidate(path)) {
@@ -7981,7 +8394,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
7981
8394
  const redacted = redactSensitiveContent(extracted, { file });
7982
8395
  const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
7983
8396
  const sourceSlug = slugify2(basename2(file, extname(file)));
7984
- const relSource = relative3(sourceRoot, file).split(sep3).join("/");
8397
+ const relSource = relative4(sourceRoot, file).split(sep3).join("/");
7985
8398
  const entry = {
7986
8399
  ...baseEntry,
7987
8400
  status: "ready",
@@ -7998,8 +8411,8 @@ async function writeImportCapture(vault, entry, today) {
7998
8411
  const content = hiddenString(entry, "__content");
7999
8412
  const project = hiddenString(entry, "__project");
8000
8413
  const relPath = await availableImportPath(vault, entry.proposed_path);
8001
- const absPath = join27(vault, relPath);
8002
- await mkdir7(join27(vault, "raw", "transcripts"), { recursive: true });
8414
+ const absPath = join28(vault, relPath);
8415
+ await mkdir7(join28(vault, "raw", "transcripts"), { recursive: true });
8003
8416
  await writeFile5(absPath, renderImportCapture(entry, content, project, today), "utf8");
8004
8417
  const validation = await runValidate({ file: absPath });
8005
8418
  return {
@@ -8015,7 +8428,7 @@ async function availableImportPath(vault, proposed) {
8015
8428
  const stem = proposed.slice(0, -ext.length);
8016
8429
  let candidate = proposed;
8017
8430
  let i = 2;
8018
- while (await readIfExists2(join27(vault, candidate))) {
8431
+ while (await readIfExists2(join28(vault, candidate))) {
8019
8432
  candidate = `${stem}-${i}${ext}`;
8020
8433
  i++;
8021
8434
  }
@@ -8200,10 +8613,10 @@ function memoryCacheRelPath(project) {
8200
8613
  }
8201
8614
  async function readMemoryCache(vault, project) {
8202
8615
  if (project) {
8203
- const projectCache = await readIfExists2(join27(vault, memoryCacheRelPath(project)));
8616
+ const projectCache = await readIfExists2(join28(vault, memoryCacheRelPath(project)));
8204
8617
  if (projectCache) return projectCache;
8205
8618
  }
8206
- return readIfExists2(join27(vault, ".skillwiki", "memory-topics.json"));
8619
+ return readIfExists2(join28(vault, ".skillwiki", "memory-topics.json"));
8207
8620
  }
8208
8621
  function dedupePages(pages) {
8209
8622
  const seen = /* @__PURE__ */ new Set();
@@ -8295,7 +8708,7 @@ function slugify2(value) {
8295
8708
 
8296
8709
  // src/commands/query.ts
8297
8710
  import { readFile as readFile18, stat as stat6 } from "fs/promises";
8298
- import { join as join28 } from "path";
8711
+ import { join as join29 } from "path";
8299
8712
  var W_KEYWORD = 2;
8300
8713
  var W_SOURCE_OVERLAP = 4;
8301
8714
  var W_WIKILINK = 3;
@@ -8416,7 +8829,7 @@ function computeKeywordScore(terms, title, tags, body) {
8416
8829
  return score;
8417
8830
  }
8418
8831
  async function loadOrBuildGraph(vault) {
8419
- const graphPath = join28(vault, ".skillwiki", "graph.json");
8832
+ const graphPath = join29(vault, ".skillwiki", "graph.json");
8420
8833
  let needsBuild = false;
8421
8834
  try {
8422
8835
  const fileStat = await stat6(graphPath);
@@ -8445,7 +8858,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8445
8858
  import { z } from "zod";
8446
8859
 
8447
8860
  // src/mcp/vault-resolve.ts
8448
- import { join as join29, resolve as resolve7 } from "path";
8861
+ import { join as join30, resolve as resolve7 } from "path";
8449
8862
 
8450
8863
  // src/mcp/allowlist.ts
8451
8864
  import { resolve as resolve6, sep as sep4 } from "path";
@@ -8507,7 +8920,7 @@ async function resolveMcpVault(input) {
8507
8920
  return ok({ vault: vaultPath, source });
8508
8921
  }
8509
8922
  function defaultGraphOut(vault) {
8510
- return join29(vault, ".skillwiki", "graph.json");
8923
+ return join30(vault, ".skillwiki", "graph.json");
8511
8924
  }
8512
8925
 
8513
8926
  // src/mcp/result-format.ts
@@ -8524,7 +8937,7 @@ function formatToolResult(payload) {
8524
8937
  // src/mcp/audit-log.ts
8525
8938
  import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
8526
8939
  import { homedir } from "os";
8527
- import { join as join30 } from "path";
8940
+ import { join as join31 } from "path";
8528
8941
  function auditEnabled() {
8529
8942
  const v = process.env.SKILLWIKI_MCP_AUDIT;
8530
8943
  if (v === "0" || v === "false") return false;
@@ -8536,7 +8949,7 @@ function auditSink() {
8536
8949
  function auditFilePath() {
8537
8950
  const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
8538
8951
  if (custom && custom.length > 0) return custom;
8539
- return join30(homedir(), ".skillwiki", "mcp-audit.jsonl");
8952
+ return join31(homedir(), ".skillwiki", "mcp-audit.jsonl");
8540
8953
  }
8541
8954
  function auditMcpToolCall(entry) {
8542
8955
  if (!auditEnabled()) return;
@@ -8546,7 +8959,7 @@ function auditMcpToolCall(entry) {
8546
8959
  return;
8547
8960
  }
8548
8961
  const path = auditFilePath();
8549
- mkdirSync5(join30(path, ".."), { recursive: true });
8962
+ mkdirSync5(join31(path, ".."), { recursive: true });
8550
8963
  appendFileSync(path, line, "utf8");
8551
8964
  }
8552
8965
  async function runMcpToolHandler(tool, input, fn) {
@@ -8767,7 +9180,7 @@ function registerMcpMutatingTools(server) {
8767
9180
 
8768
9181
  // src/mcp/resources.ts
8769
9182
  import { readFile as readFile20 } from "fs/promises";
8770
- import { join as join32 } from "path";
9183
+ import { join as join33 } from "path";
8771
9184
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
8772
9185
 
8773
9186
  // src/mcp/lint-bucket.ts
@@ -8896,8 +9309,8 @@ async function fetchQueryPreview(input) {
8896
9309
 
8897
9310
  // src/mcp/graph-html.ts
8898
9311
  import { readFile as readFile19 } from "fs/promises";
8899
- import { join as join31 } from "path";
8900
- import { existsSync as existsSync15 } from "fs";
9312
+ import { join as join32 } from "path";
9313
+ import { existsSync as existsSync16 } from "fs";
8901
9314
  var TYPE_COLORS = {
8902
9315
  entities: "#e74c3c",
8903
9316
  concepts: "#27ae60",
@@ -8965,9 +9378,9 @@ ${nodeSvg}
8965
9378
  return { html, node_count: nodes.length, edge_count: edges.length, truncated };
8966
9379
  }
8967
9380
  async function fetchGraphHtmlReport(input) {
8968
- const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
9381
+ const graphPath = input.graphPath ?? join32(input.vault, ".skillwiki", "graph.json");
8969
9382
  const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
8970
- if (!existsSync15(graphPath)) {
9383
+ if (!existsSync16(graphPath)) {
8971
9384
  return {
8972
9385
  exitCode: ExitCode.FILE_NOT_FOUND,
8973
9386
  result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
@@ -9039,7 +9452,7 @@ async function fetchStaleSummary(input) {
9039
9452
 
9040
9453
  // src/mcp/resources.ts
9041
9454
  async function readVaultFile(vault, rel) {
9042
- return readFile20(join32(vault, rel), "utf8");
9455
+ return readFile20(join33(vault, rel), "utf8");
9043
9456
  }
9044
9457
  async function tailLines(text, lines) {
9045
9458
  const parts = text.split(/\r?\n/);
@@ -9125,7 +9538,7 @@ function registerMcpResources(server) {
9125
9538
  if (!v.ok) {
9126
9539
  return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
9127
9540
  }
9128
- const path = join32(v.data.vault, ".skillwiki", "graph.json");
9541
+ const path = join33(v.data.vault, ".skillwiki", "graph.json");
9129
9542
  try {
9130
9543
  const raw = await readFile20(path, "utf8");
9131
9544
  const graph = JSON.parse(raw);
@@ -9500,6 +9913,13 @@ export {
9500
9913
  evaluateSatelliteRunHealth,
9501
9914
  runDoctor,
9502
9915
  readCliPackageJson,
9916
+ DEFAULT_DIRTY_VOLUME_THRESHOLD,
9917
+ DEFAULT_CAPTURE_BUDGET,
9918
+ DEFAULT_NO_DECISION_STREAK,
9919
+ GateError,
9920
+ evaluateDirtyVolumeGate,
9921
+ CAPTURE_HYGIENE_CONTRACT,
9922
+ runWritePreflight,
9503
9923
  runObserve,
9504
9924
  memoryAuthorityTiersRank,
9505
9925
  runMemoryTopics,
package/dist/cli.js CHANGED
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CAPTURE_HYGIENE_CONTRACT,
4
+ DEFAULT_CAPTURE_BUDGET,
5
+ DEFAULT_DIRTY_VOLUME_THRESHOLD,
6
+ DEFAULT_NO_DECISION_STREAK,
7
+ GateError,
3
8
  SATELLITE_STALE_MS,
4
9
  acquireLock,
5
10
  acquireOwnedSyncLock,
@@ -9,6 +14,7 @@ import {
9
14
  buildRemoteObjectPath,
10
15
  clearLastOp,
11
16
  configPath,
17
+ evaluateDirtyVolumeGate,
12
18
  evaluateSatelliteRunHealth,
13
19
  extractCitationMarkers,
14
20
  extractTaxonomy,
@@ -66,13 +72,14 @@ import {
66
72
  runTagAudit,
67
73
  runTopicMapCheck,
68
74
  runValidate,
75
+ runWritePreflight,
69
76
  safeWritePage,
70
77
  satelliteLatestRunPath,
71
78
  scanConflictMarkerBlocksInText,
72
79
  taxonomyCommentForPage,
73
80
  upsertIndexEntry,
74
81
  writeLogEvent
75
- } from "./chunk-AVCGIWSL.js";
82
+ } from "./chunk-P2FCRGKU.js";
76
83
  import {
77
84
  normalizeDistTag,
78
85
  readCache,
@@ -7086,6 +7093,67 @@ async function postCommit(vault, exitCode) {
7086
7093
  clearLastOp(vault);
7087
7094
  }
7088
7095
 
7096
+ // src/commands/write-preflight.ts
7097
+ import { existsSync as existsSync13, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
7098
+ async function runWritePreflightCommand(input) {
7099
+ if (!existsSync13(input.vault) || !statSync4(input.vault).isDirectory()) {
7100
+ return {
7101
+ exitCode: ExitCode.VAULT_PATH_INVALID,
7102
+ result: err(GateError.VAULT_PATH_INVALID, { path: input.vault })
7103
+ };
7104
+ }
7105
+ let priorText = input.priorArtifactText;
7106
+ if (input.priorArtifactFile) {
7107
+ if (!existsSync13(input.priorArtifactFile)) {
7108
+ return {
7109
+ exitCode: ExitCode.FILE_NOT_FOUND,
7110
+ result: err("FILE_NOT_FOUND", { path: input.priorArtifactFile })
7111
+ };
7112
+ }
7113
+ priorText = readFileSync15(input.priorArtifactFile, "utf8");
7114
+ }
7115
+ const checkList = input.checks ? input.checks.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
7116
+ const result = runWritePreflight({
7117
+ vault: input.vault,
7118
+ command: input.command,
7119
+ dirtyThreshold: input.dirtyThreshold ?? DEFAULT_DIRTY_VOLUME_THRESHOLD,
7120
+ skipDirty: input.skipDirty,
7121
+ priorArtifactText: priorText,
7122
+ consecutiveNoNewDecision: input.consecutiveNoNewDecision,
7123
+ noDecisionThreshold: input.noDecisionThreshold ?? DEFAULT_NO_DECISION_STREAK,
7124
+ humanAllow: input.humanAllow,
7125
+ missionKind: input.missionKind,
7126
+ skipMission: input.skipMission,
7127
+ project: input.project,
7128
+ captureDay: input.captureDay,
7129
+ captureBudget: input.captureBudget ?? DEFAULT_CAPTURE_BUDGET,
7130
+ severity: input.severity,
7131
+ skipBudget: input.skipBudget,
7132
+ checks: checkList
7133
+ });
7134
+ if (!result.ok) {
7135
+ const code = result.error === GateError.VAULT_PATH_INVALID ? ExitCode.VAULT_PATH_INVALID : ExitCode.PREFLIGHT_FAILED;
7136
+ return { exitCode: code, result };
7137
+ }
7138
+ if (!result.data.allowed) {
7139
+ const first = result.data.refused[0];
7140
+ return {
7141
+ exitCode: ExitCode.PREFLIGHT_FAILED,
7142
+ result: err(first?.code ?? GateError.VAULT_DIRTY_BACKLOG, {
7143
+ ...result.data,
7144
+ hygiene_contract: CAPTURE_HYGIENE_CONTRACT
7145
+ })
7146
+ };
7147
+ }
7148
+ return {
7149
+ exitCode: ExitCode.OK,
7150
+ result: ok({
7151
+ ...result.data,
7152
+ hygiene_contract: CAPTURE_HYGIENE_CONTRACT
7153
+ })
7154
+ };
7155
+ }
7156
+
7089
7157
  // src/cli.ts
7090
7158
  var pkg = readCliPackageJson();
7091
7159
  var program = new Command();
@@ -7097,6 +7165,29 @@ async function emit(r, vault, opts) {
7097
7165
  if (vault && opts?.postCommit !== false) await postCommit(vault, r.exitCode);
7098
7166
  process.exit(r.exitCode);
7099
7167
  }
7168
+ function dirtyVolumeBlock(vault, command) {
7169
+ if (process.env.SKILLWIKI_SKIP_DIRTY_GATE === "1") return null;
7170
+ const thrRaw = process.env.SKILLWIKI_DIRTY_THRESHOLD;
7171
+ const threshold = thrRaw != null && thrRaw !== "" ? Number(thrRaw) : void 0;
7172
+ const gate = evaluateDirtyVolumeGate({
7173
+ vault,
7174
+ command,
7175
+ threshold: Number.isFinite(threshold) ? threshold : void 0
7176
+ });
7177
+ if (gate.allowed) return null;
7178
+ return {
7179
+ exitCode: ExitCode.PREFLIGHT_FAILED,
7180
+ result: {
7181
+ ok: false,
7182
+ error: gate.code,
7183
+ detail: {
7184
+ reason: gate.reason,
7185
+ report: gate.report,
7186
+ humanHint: gate.humanHint
7187
+ }
7188
+ }
7189
+ };
7190
+ }
7100
7191
  async function emitGuardedVaultWrite(vault, command, run, opts) {
7101
7192
  const guard = await guardProtectedVaultWrite({
7102
7193
  vault,
@@ -7110,6 +7201,10 @@ async function emitGuardedVaultWrite(vault, command, run, opts) {
7110
7201
  if (guard.blocked) {
7111
7202
  return emit({ exitCode: guard.exitCode, result: guard.result }, void 0, { postCommit: false });
7112
7203
  }
7204
+ const dirty = dirtyVolumeBlock(vault, command);
7205
+ if (dirty) {
7206
+ return emit(dirty, void 0, { postCommit: false });
7207
+ }
7113
7208
  return emit(await run(), vault, opts);
7114
7209
  }
7115
7210
  async function emitManagedVaultWrite(vault, command, mutate, opts) {
@@ -7125,6 +7220,10 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
7125
7220
  if (guard.blocked) {
7126
7221
  return emit({ exitCode: guard.exitCode, result: guard.result }, void 0, { postCommit: false });
7127
7222
  }
7223
+ const dirty = dirtyVolumeBlock(vault, command);
7224
+ if (dirty) {
7225
+ return emit(dirty, void 0, { postCommit: false });
7226
+ }
7128
7227
  const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-7TWSEZJZ.js");
7129
7228
  const run = await runManagedWriteTransaction2({
7130
7229
  vault,
@@ -7815,7 +7914,7 @@ program.command("seed [vault]").description("populate a vault with example conte
7815
7914
  () => runSeed({ vault: v.vault })
7816
7915
  );
7817
7916
  });
7818
- program.command("observe [vault]").description("create a raw transcript observation entry").requiredOption("--text <text>", "observation text").option("--kind <kind>", "observation kind (note|bug|task|idea|session-log)", "task").option("--project <slug>", "associated project slug (required for task/bug claim detection)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7917
+ program.command("observe [vault]").description("create a raw transcript observation entry").requiredOption("--text <text>", "observation text").option("--kind <kind>", "observation kind (note|bug|task|idea|session-log)", "task").option("--project <slug>", "associated project slug (required for task/bug claim detection)").option("--severity <level>", "severity (P0 escapes daily capture budget)").option("--capture-budget <n>", "override daily capture budget", (s) => parseInt(s, 10)).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7819
7918
  const v = await resolveVaultArg(vault, opts.wiki);
7820
7919
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7821
7920
  else return emitGuardedVaultWrite(
@@ -7825,7 +7924,9 @@ program.command("observe [vault]").description("create a raw transcript observat
7825
7924
  vault: v.vault,
7826
7925
  text: opts.text,
7827
7926
  kind: opts.kind,
7828
- project: opts.project
7927
+ project: opts.project,
7928
+ severity: opts.severity,
7929
+ captureBudget: opts.captureBudget
7829
7930
  })
7830
7931
  );
7831
7932
  });
@@ -7985,6 +8086,29 @@ fleetCmd.command("health [vault]").description("read-only health probe for skill
7985
8086
  });
7986
8087
  emit(r, vault);
7987
8088
  });
8089
+ program.command("write-preflight [vault]").description("agent write gates: dirty volume, saturated mission stop, capture budget (analysis M1\u2013M3)").option("--command <name>", "command name for hygiene classification", "agent-write").option("--dirty-threshold <n>", "expanded dirty-file threshold", (s) => parseInt(s, 10)).option("--skip-dirty", "skip dirty-volume gate", false).option("--prior-artifact-file <path>", "prior cycle artifact path for saturation scan").option("--prior-artifact-text <text>", "prior cycle artifact text for saturation scan").option("--consecutive-no-decision <n>", "no-new-decision streak", (s) => parseInt(s, 10)).option("--no-decision-threshold <n>", "streak threshold", (s) => parseInt(s, 10)).option("--human-allow", "explicit human allow for saturated missions", false).option("--mission-kind <kind>", "mission kind label (pilot-q, research-cycle, \u2026)").option("--skip-mission", "skip mission saturation gate", false).option("--project <slug>", "project slug for capture budget").option("--capture-day <date>", "YYYY-MM-DD for capture budget").option("--capture-budget <n>", "daily capture budget", (s) => parseInt(s, 10)).option("--severity <level>", "severity (P0 escapes budget)").option("--skip-budget", "skip capture budget gate", false).option("--checks <list>", "comma list: dirty,mission,budget,all", "all").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
8090
+ const v = await resolveVaultArg(vault, opts.wiki);
8091
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
8092
+ else emit(await runWritePreflightCommand({
8093
+ vault: v.vault,
8094
+ command: opts.command,
8095
+ dirtyThreshold: opts.dirtyThreshold,
8096
+ skipDirty: !!opts.skipDirty,
8097
+ priorArtifactFile: opts.priorArtifactFile,
8098
+ priorArtifactText: opts.priorArtifactText,
8099
+ consecutiveNoNewDecision: opts.consecutiveNoDecision,
8100
+ noDecisionThreshold: opts.noDecisionThreshold,
8101
+ humanAllow: !!opts.humanAllow,
8102
+ missionKind: opts.missionKind,
8103
+ skipMission: !!opts.skipMission,
8104
+ project: opts.project,
8105
+ captureDay: opts.captureDay,
8106
+ captureBudget: opts.captureBudget,
8107
+ severity: opts.severity,
8108
+ skipBudget: !!opts.skipBudget,
8109
+ checks: opts.checks
8110
+ }), void 0, { postCommit: false });
8111
+ });
7988
8112
  program.command("mcp").description("start stdio Model Context Protocol server (read-only vault tools)").action(async () => {
7989
8113
  await runSkillwikiMcpStdio();
7990
8114
  });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-AVCGIWSL.js";
4
+ } from "./chunk-P2FCRGKU.js";
5
5
  import "./chunk-7I2TPIV5.js";
6
6
  import "./chunk-U34B2XQJ.js";
7
7
  import "./chunk-TYN2IHBY.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.9",
3
+ "version": "0.10.11",
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.9",
3
+ "version": "0.10.11",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 19 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.9",
3
+ "version": "0.10.11",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 19 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.9",
3
+ "version": "0.10.11",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",