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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
7130
|
-
import { join as
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
7333
|
-
await mkdir7(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8002
|
-
await mkdir7(
|
|
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(
|
|
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(
|
|
8616
|
+
const projectCache = await readIfExists2(join28(vault, memoryCacheRelPath(project)));
|
|
8204
8617
|
if (projectCache) return projectCache;
|
|
8205
8618
|
}
|
|
8206
|
-
return readIfExists2(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
8900
|
-
import { existsSync as
|
|
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 ??
|
|
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 (!
|
|
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(
|
|
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 =
|
|
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-
|
|
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
|
});
|
package/dist/skillwiki-mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
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": {
|