skillwiki 0.10.9 → 0.10.10
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");
|
|
@@ -7124,10 +7126,389 @@ 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 { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
|
|
7131
|
+
import { join as join26, relative as relative3 } from "path";
|
|
7132
|
+
var DEFAULT_DIRTY_VOLUME_THRESHOLD = 50;
|
|
7133
|
+
var DEFAULT_CAPTURE_BUDGET = 20;
|
|
7134
|
+
var DEFAULT_NO_DECISION_STREAK = 3;
|
|
7135
|
+
var GateError = {
|
|
7136
|
+
VAULT_DIRTY_BACKLOG: "VAULT_DIRTY_BACKLOG",
|
|
7137
|
+
DIMINISHING_RETURNS: "DIMINISHING_RETURNS",
|
|
7138
|
+
CAPTURE_BUDGET_EXHAUSTED: "CAPTURE_BUDGET_EXHAUSTED",
|
|
7139
|
+
USAGE: "USAGE",
|
|
7140
|
+
VAULT_PATH_INVALID: "VAULT_PATH_INVALID"
|
|
7141
|
+
};
|
|
7142
|
+
var HYGIENE_COMMANDS = /* @__PURE__ */ new Set([
|
|
7143
|
+
"write-preflight",
|
|
7144
|
+
"sync status",
|
|
7145
|
+
"sync push",
|
|
7146
|
+
"sync pull",
|
|
7147
|
+
"sync lock",
|
|
7148
|
+
"sync unlock",
|
|
7149
|
+
"sync resolve-derived",
|
|
7150
|
+
"sync journal list",
|
|
7151
|
+
"sync journal clear-stale",
|
|
7152
|
+
"sync lint-delta",
|
|
7153
|
+
"sync peers",
|
|
7154
|
+
"work-complete",
|
|
7155
|
+
"work-validate",
|
|
7156
|
+
"log materialize",
|
|
7157
|
+
"log migrate-legacy",
|
|
7158
|
+
"index rebuild",
|
|
7159
|
+
"projections materialize",
|
|
7160
|
+
"doctor",
|
|
7161
|
+
"health",
|
|
7162
|
+
"lint",
|
|
7163
|
+
"status",
|
|
7164
|
+
"path",
|
|
7165
|
+
"fleet context",
|
|
7166
|
+
"fleet validate",
|
|
7167
|
+
"fleet health"
|
|
7168
|
+
]);
|
|
7169
|
+
function isHygieneCommand(command) {
|
|
7170
|
+
const c = command.trim().toLowerCase().replace(/\s+/g, " ");
|
|
7171
|
+
if (HYGIENE_COMMANDS.has(c)) return true;
|
|
7172
|
+
if (c === "sync" || c.startsWith("sync ")) {
|
|
7173
|
+
const rest = c.slice(5).trim();
|
|
7174
|
+
if (!rest) return true;
|
|
7175
|
+
return HYGIENE_COMMANDS.has(`sync ${rest}`);
|
|
7176
|
+
}
|
|
7177
|
+
return false;
|
|
7178
|
+
}
|
|
7179
|
+
function measureDirtyVolume(vault) {
|
|
7180
|
+
const empty = (extra = {}) => ({
|
|
7181
|
+
porcelain_lines: 0,
|
|
7182
|
+
expanded_files: 0,
|
|
7183
|
+
modified: 0,
|
|
7184
|
+
untracked: 0,
|
|
7185
|
+
buckets: [],
|
|
7186
|
+
threshold: 0,
|
|
7187
|
+
over_threshold: false,
|
|
7188
|
+
is_git_repo: false,
|
|
7189
|
+
...extra
|
|
7190
|
+
});
|
|
7191
|
+
if (!existsSync14(vault) || !statSync3(vault).isDirectory()) {
|
|
7192
|
+
return empty();
|
|
7193
|
+
}
|
|
7194
|
+
const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
7195
|
+
if (!gitDir) {
|
|
7196
|
+
return empty({ is_git_repo: false });
|
|
7197
|
+
}
|
|
7198
|
+
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
7199
|
+
const lines = porcelain ? porcelain.split("\n").filter((l) => l.trim().length > 0) : [];
|
|
7200
|
+
let modified = 0;
|
|
7201
|
+
let untracked = 0;
|
|
7202
|
+
let expanded = 0;
|
|
7203
|
+
const bucketMap = /* @__PURE__ */ new Map();
|
|
7204
|
+
const addBucket = (rel, n) => {
|
|
7205
|
+
const top = rel.split(/[/\\]/)[0] || ".";
|
|
7206
|
+
bucketMap.set(top, (bucketMap.get(top) ?? 0) + n);
|
|
7207
|
+
};
|
|
7208
|
+
for (const line of lines) {
|
|
7209
|
+
const code = line.slice(0, 2);
|
|
7210
|
+
let pathPart = line.slice(3);
|
|
7211
|
+
if (pathPart.includes(" -> ")) {
|
|
7212
|
+
pathPart = pathPart.split(" -> ").pop() ?? pathPart;
|
|
7213
|
+
}
|
|
7214
|
+
const rel = pathPart.replace(/^"|"$/g, "").trim();
|
|
7215
|
+
if (!rel) continue;
|
|
7216
|
+
if (code === "??") {
|
|
7217
|
+
untracked += 1;
|
|
7218
|
+
const abs = join26(vault, rel);
|
|
7219
|
+
if (existsSync14(abs) && statSync3(abs).isDirectory()) {
|
|
7220
|
+
const files = listFilesRecursive(abs);
|
|
7221
|
+
expanded += files.length;
|
|
7222
|
+
addBucket(rel, files.length);
|
|
7223
|
+
} else {
|
|
7224
|
+
expanded += 1;
|
|
7225
|
+
addBucket(rel, 1);
|
|
7226
|
+
}
|
|
7227
|
+
} else {
|
|
7228
|
+
modified += 1;
|
|
7229
|
+
expanded += 1;
|
|
7230
|
+
addBucket(rel, 1);
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
const buckets = [...bucketMap.entries()].map(([bucket, count]) => ({ bucket, count })).sort((a, b) => b.count - a.count);
|
|
7234
|
+
return {
|
|
7235
|
+
porcelain_lines: lines.length,
|
|
7236
|
+
expanded_files: expanded,
|
|
7237
|
+
modified,
|
|
7238
|
+
untracked,
|
|
7239
|
+
buckets,
|
|
7240
|
+
threshold: 0,
|
|
7241
|
+
over_threshold: false,
|
|
7242
|
+
is_git_repo: true
|
|
7243
|
+
};
|
|
7244
|
+
}
|
|
7245
|
+
function listFilesRecursive(dir) {
|
|
7246
|
+
const out = [];
|
|
7247
|
+
let entries;
|
|
7248
|
+
try {
|
|
7249
|
+
entries = readdirSync4(dir);
|
|
7250
|
+
} catch {
|
|
7251
|
+
return out;
|
|
7252
|
+
}
|
|
7253
|
+
for (const name of entries) {
|
|
7254
|
+
if (name === ".git") continue;
|
|
7255
|
+
const p = join26(dir, name);
|
|
7256
|
+
try {
|
|
7257
|
+
const st = statSync3(p);
|
|
7258
|
+
if (st.isDirectory()) out.push(...listFilesRecursive(p));
|
|
7259
|
+
else if (st.isFile()) out.push(p);
|
|
7260
|
+
} catch {
|
|
7261
|
+
}
|
|
7262
|
+
}
|
|
7263
|
+
return out;
|
|
7264
|
+
}
|
|
7265
|
+
function evaluateDirtyVolumeGate(input) {
|
|
7266
|
+
const threshold = input.threshold ?? DEFAULT_DIRTY_VOLUME_THRESHOLD;
|
|
7267
|
+
const report = measureDirtyVolume(input.vault);
|
|
7268
|
+
report.threshold = threshold;
|
|
7269
|
+
report.over_threshold = report.is_git_repo && report.expanded_files > threshold;
|
|
7270
|
+
if (input.skip) {
|
|
7271
|
+
return { allowed: true, reason: "skipped", report };
|
|
7272
|
+
}
|
|
7273
|
+
if (input.command && isHygieneCommand(input.command)) {
|
|
7274
|
+
return { allowed: true, reason: "hygiene", report };
|
|
7275
|
+
}
|
|
7276
|
+
if (!report.is_git_repo) {
|
|
7277
|
+
return { allowed: true, reason: "not_a_git_repo", report };
|
|
7278
|
+
}
|
|
7279
|
+
if (!report.over_threshold) {
|
|
7280
|
+
return { allowed: true, reason: "under_threshold", report };
|
|
7281
|
+
}
|
|
7282
|
+
const top = report.buckets.slice(0, 5).map((b) => `${b.bucket}:${b.count}`).join(", ");
|
|
7283
|
+
return {
|
|
7284
|
+
allowed: false,
|
|
7285
|
+
reason: "over_threshold",
|
|
7286
|
+
code: GateError.VAULT_DIRTY_BACKLOG,
|
|
7287
|
+
report,
|
|
7288
|
+
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.`
|
|
7289
|
+
};
|
|
7290
|
+
}
|
|
7291
|
+
var SATURATION_PATTERNS = [
|
|
7292
|
+
/\benablement\s+\*\*saturated\*\*/i,
|
|
7293
|
+
/\bsaturated\b/i,
|
|
7294
|
+
/\bexplicit hold\b/i,
|
|
7295
|
+
/\bno further\b.*\bbatches\b/i,
|
|
7296
|
+
/\bpause (?:the )?job\b/i,
|
|
7297
|
+
/\bcancel research\b/i,
|
|
7298
|
+
/\bkill research\b/i,
|
|
7299
|
+
/\bdiminishing returns\b/i,
|
|
7300
|
+
/\bhuman send only\b/i,
|
|
7301
|
+
/\bno new decision\b/i,
|
|
7302
|
+
/\bHOLD:\b/,
|
|
7303
|
+
/\bstatus:\s*saturated\b/i,
|
|
7304
|
+
/\bmission:\s*cancelled\b/i,
|
|
7305
|
+
/\bcancelled mission\b/i
|
|
7306
|
+
];
|
|
7307
|
+
function detectSaturationSignals(text) {
|
|
7308
|
+
if (!text) return [];
|
|
7309
|
+
const hits = [];
|
|
7310
|
+
for (const re of SATURATION_PATTERNS) {
|
|
7311
|
+
const m = text.match(re);
|
|
7312
|
+
if (m) hits.push(m[0]);
|
|
7313
|
+
}
|
|
7314
|
+
return hits;
|
|
7315
|
+
}
|
|
7316
|
+
function evaluateMissionCycleGate(input) {
|
|
7317
|
+
if (input.humanAllow) {
|
|
7318
|
+
return { allowed: true, reason: "human_allow", signals: [] };
|
|
7319
|
+
}
|
|
7320
|
+
const signals = detectSaturationSignals(input.priorArtifactText ?? "");
|
|
7321
|
+
if (signals.length > 0) {
|
|
7322
|
+
return {
|
|
7323
|
+
allowed: false,
|
|
7324
|
+
reason: "saturated_text",
|
|
7325
|
+
code: GateError.DIMINISHING_RETURNS,
|
|
7326
|
+
signals,
|
|
7327
|
+
humanHint: `Mission saturated/hold/stop signal(s) in prior artifact: ${signals.slice(0, 5).join("; ")}. Refuse new ${input.missionKind ?? "cycle"} artifact without --human-allow.`
|
|
7328
|
+
};
|
|
7329
|
+
}
|
|
7330
|
+
const streak = input.consecutiveNoNewDecision ?? 0;
|
|
7331
|
+
const thresh = input.noDecisionThreshold ?? DEFAULT_NO_DECISION_STREAK;
|
|
7332
|
+
if (streak >= thresh) {
|
|
7333
|
+
return {
|
|
7334
|
+
allowed: false,
|
|
7335
|
+
reason: "no_decision_streak",
|
|
7336
|
+
code: GateError.DIMINISHING_RETURNS,
|
|
7337
|
+
signals: [`no_new_decision_streak=${streak}`],
|
|
7338
|
+
humanHint: `${streak} consecutive no-new-decision cycles (\u2265 ${thresh}). Refuse further cycle artifacts without --human-allow.`
|
|
7339
|
+
};
|
|
7340
|
+
}
|
|
7341
|
+
return { allowed: true, reason: "clean", signals: [] };
|
|
7342
|
+
}
|
|
7343
|
+
function utcDay(d = /* @__PURE__ */ new Date()) {
|
|
7344
|
+
return d.toISOString().slice(0, 10);
|
|
7345
|
+
}
|
|
7346
|
+
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.`;
|
|
7347
|
+
function listProjectDayCaptures(vault, project, day) {
|
|
7348
|
+
const found = [];
|
|
7349
|
+
const slug = project.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
|
|
7350
|
+
if (!slug || !existsSync14(vault)) return found;
|
|
7351
|
+
const consider = (abs, rel) => {
|
|
7352
|
+
if (!rel.endsWith(".md")) return;
|
|
7353
|
+
const base = rel.split(/[/\\]/).pop() ?? "";
|
|
7354
|
+
if (!base.startsWith(day)) return;
|
|
7355
|
+
const normRel = rel.replace(/\\/g, "/");
|
|
7356
|
+
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);
|
|
7357
|
+
if (!captureLike && !/transcripts/.test(normRel)) {
|
|
7358
|
+
return;
|
|
7359
|
+
}
|
|
7360
|
+
const norm = rel.replace(/\\/g, "/");
|
|
7361
|
+
if (norm.startsWith(`projects/${slug}/`)) {
|
|
7362
|
+
found.push(norm);
|
|
7363
|
+
return;
|
|
7364
|
+
}
|
|
7365
|
+
if (norm.startsWith("raw/transcripts/")) {
|
|
7366
|
+
try {
|
|
7367
|
+
const body = readFileSync12(abs, "utf8");
|
|
7368
|
+
if (body.includes(`project: ${slug}`) || body.includes(`project: "[[${slug}]]"`) || body.includes(`project: [[${slug}]]`)) {
|
|
7369
|
+
found.push(norm);
|
|
7370
|
+
} else if (base.includes(slug)) {
|
|
7371
|
+
found.push(norm);
|
|
7372
|
+
}
|
|
7373
|
+
} catch {
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7376
|
+
};
|
|
7377
|
+
const walk = (dir, relBase) => {
|
|
7378
|
+
if (!existsSync14(dir)) return;
|
|
7379
|
+
let entries;
|
|
7380
|
+
try {
|
|
7381
|
+
entries = readdirSync4(dir);
|
|
7382
|
+
} catch {
|
|
7383
|
+
return;
|
|
7384
|
+
}
|
|
7385
|
+
for (const name of entries) {
|
|
7386
|
+
const abs = join26(dir, name);
|
|
7387
|
+
const rel = relBase ? `${relBase}/${name}` : name;
|
|
7388
|
+
try {
|
|
7389
|
+
const st = statSync3(abs);
|
|
7390
|
+
if (st.isDirectory()) walk(abs, rel);
|
|
7391
|
+
else if (st.isFile()) consider(abs, rel);
|
|
7392
|
+
} catch {
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
};
|
|
7396
|
+
walk(join26(vault, "raw", "transcripts"), "raw/transcripts");
|
|
7397
|
+
walk(join26(vault, "projects", slug, "raw", "transcripts"), `projects/${slug}/raw/transcripts`);
|
|
7398
|
+
walk(join26(vault, "projects", slug, "requirements"), `projects/${slug}/requirements`);
|
|
7399
|
+
const workRoot = join26(vault, "projects", slug, "work");
|
|
7400
|
+
if (existsSync14(workRoot)) {
|
|
7401
|
+
try {
|
|
7402
|
+
for (const name of readdirSync4(workRoot)) {
|
|
7403
|
+
if (!name.startsWith(day)) continue;
|
|
7404
|
+
if (!/investigate|pilot-q|research|cycle|dev-loop/.test(name)) continue;
|
|
7405
|
+
const abs = join26(workRoot, name);
|
|
7406
|
+
if (statSync3(abs).isDirectory()) {
|
|
7407
|
+
for (const f of listFilesRecursive(abs)) {
|
|
7408
|
+
const rel = relative3(vault, f).replace(/\\/g, "/");
|
|
7409
|
+
if (rel.endsWith(".md")) found.push(rel);
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
}
|
|
7413
|
+
} catch {
|
|
7414
|
+
}
|
|
7415
|
+
}
|
|
7416
|
+
return [...new Set(found)].sort();
|
|
7417
|
+
}
|
|
7418
|
+
function evaluateCaptureBudget(input) {
|
|
7419
|
+
const day = input.day ?? utcDay();
|
|
7420
|
+
const budget = input.budget ?? DEFAULT_CAPTURE_BUDGET;
|
|
7421
|
+
const paths = listProjectDayCaptures(input.vault, input.project, day);
|
|
7422
|
+
const used = paths.length;
|
|
7423
|
+
const report = {
|
|
7424
|
+
project: input.project,
|
|
7425
|
+
day,
|
|
7426
|
+
budget,
|
|
7427
|
+
used,
|
|
7428
|
+
remaining: Math.max(0, budget - used),
|
|
7429
|
+
paths
|
|
7430
|
+
};
|
|
7431
|
+
if (input.skip) {
|
|
7432
|
+
return { allowed: true, reason: "skipped", report };
|
|
7433
|
+
}
|
|
7434
|
+
const sev = (input.severity ?? "").toString().trim().toUpperCase();
|
|
7435
|
+
if (sev === "P0" || sev === "0") {
|
|
7436
|
+
return { allowed: true, reason: "p0_escape", report };
|
|
7437
|
+
}
|
|
7438
|
+
if (used >= budget) {
|
|
7439
|
+
return {
|
|
7440
|
+
allowed: false,
|
|
7441
|
+
reason: "exhausted",
|
|
7442
|
+
code: GateError.CAPTURE_BUDGET_EXHAUSTED,
|
|
7443
|
+
report,
|
|
7444
|
+
humanHint: `Capture budget exhausted for project ${input.project} on ${day}: ${used}/${budget}. ${CAPTURE_HYGIENE_CONTRACT}`
|
|
7445
|
+
};
|
|
7446
|
+
}
|
|
7447
|
+
return { allowed: true, reason: "under_budget", report };
|
|
7448
|
+
}
|
|
7449
|
+
function runWritePreflight(input) {
|
|
7450
|
+
if (!input.vault || !existsSync14(input.vault)) {
|
|
7451
|
+
return err(GateError.VAULT_PATH_INVALID, { path: input.vault });
|
|
7452
|
+
}
|
|
7453
|
+
const want = new Set(input.checks ?? ["all"]);
|
|
7454
|
+
const runAll = want.has("all");
|
|
7455
|
+
const checks = {};
|
|
7456
|
+
const refused = [];
|
|
7457
|
+
if (runAll || want.has("dirty")) {
|
|
7458
|
+
const dirty = evaluateDirtyVolumeGate({
|
|
7459
|
+
vault: input.vault,
|
|
7460
|
+
threshold: input.dirtyThreshold,
|
|
7461
|
+
skip: input.skipDirty,
|
|
7462
|
+
command: input.command
|
|
7463
|
+
});
|
|
7464
|
+
checks.dirty = dirty;
|
|
7465
|
+
if (!dirty.allowed) {
|
|
7466
|
+
refused.push({ code: dirty.code, humanHint: dirty.humanHint });
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
const missionRequested = runAll || want.has("mission") || input.priorArtifactText != null || input.consecutiveNoNewDecision != null;
|
|
7470
|
+
if (missionRequested && !input.skipMission) {
|
|
7471
|
+
if (want.has("mission") || input.priorArtifactText != null && input.priorArtifactText.length > 0 || input.consecutiveNoNewDecision != null && input.consecutiveNoNewDecision > 0) {
|
|
7472
|
+
const mission = evaluateMissionCycleGate({
|
|
7473
|
+
priorArtifactText: input.priorArtifactText,
|
|
7474
|
+
consecutiveNoNewDecision: input.consecutiveNoNewDecision,
|
|
7475
|
+
noDecisionThreshold: input.noDecisionThreshold,
|
|
7476
|
+
humanAllow: input.humanAllow,
|
|
7477
|
+
missionKind: input.missionKind
|
|
7478
|
+
});
|
|
7479
|
+
checks.mission = mission;
|
|
7480
|
+
if (!mission.allowed) {
|
|
7481
|
+
refused.push({ code: mission.code, humanHint: mission.humanHint });
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
if ((runAll || want.has("budget")) && input.project && !input.skipBudget) {
|
|
7486
|
+
const budget = evaluateCaptureBudget({
|
|
7487
|
+
vault: input.vault,
|
|
7488
|
+
project: input.project,
|
|
7489
|
+
day: input.captureDay,
|
|
7490
|
+
budget: input.captureBudget,
|
|
7491
|
+
severity: input.severity
|
|
7492
|
+
});
|
|
7493
|
+
checks.budget = budget;
|
|
7494
|
+
if (!budget.allowed) {
|
|
7495
|
+
refused.push({ code: budget.code, humanHint: budget.humanHint });
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
const allowed = refused.length === 0;
|
|
7499
|
+
return ok({
|
|
7500
|
+
allowed,
|
|
7501
|
+
checks,
|
|
7502
|
+
refused,
|
|
7503
|
+
hygiene_contract: CAPTURE_HYGIENE_CONTRACT,
|
|
7504
|
+
humanHint: allowed ? "write preflight: allowed" : `write preflight: refused \u2014 ${refused.map((r) => r.code).join(", ")}`
|
|
7505
|
+
});
|
|
7506
|
+
}
|
|
7507
|
+
|
|
7127
7508
|
// src/commands/observe.ts
|
|
7128
7509
|
import { mkdir as mkdir6, writeFile as writeFile4 } from "fs/promises";
|
|
7129
|
-
import { existsSync as
|
|
7130
|
-
import { join as
|
|
7510
|
+
import { existsSync as existsSync15, statSync as statSync4 } from "fs";
|
|
7511
|
+
import { join as join27 } from "path";
|
|
7131
7512
|
import { createHash as createHash6 } from "crypto";
|
|
7132
7513
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
7133
7514
|
function slugify(text) {
|
|
@@ -7150,13 +7531,33 @@ async function runObserve(input) {
|
|
|
7150
7531
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
7151
7532
|
};
|
|
7152
7533
|
}
|
|
7153
|
-
if (!
|
|
7534
|
+
if (!existsSync15(input.vault) || !statSync4(input.vault).isDirectory()) {
|
|
7154
7535
|
return {
|
|
7155
7536
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7156
7537
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
7157
7538
|
};
|
|
7158
7539
|
}
|
|
7159
|
-
|
|
7540
|
+
if (input.project && !input.skipCaptureBudget) {
|
|
7541
|
+
const budget = evaluateCaptureBudget({
|
|
7542
|
+
vault: input.vault,
|
|
7543
|
+
project: input.project,
|
|
7544
|
+
day: input.captureDay,
|
|
7545
|
+
budget: input.captureBudget,
|
|
7546
|
+
severity: input.severity
|
|
7547
|
+
});
|
|
7548
|
+
if (!budget.allowed) {
|
|
7549
|
+
return {
|
|
7550
|
+
exitCode: ExitCode.PREFLIGHT_FAILED,
|
|
7551
|
+
result: err(GateError.CAPTURE_BUDGET_EXHAUSTED, {
|
|
7552
|
+
reason: budget.reason,
|
|
7553
|
+
report: budget.report,
|
|
7554
|
+
humanHint: budget.humanHint,
|
|
7555
|
+
hygiene_contract: CAPTURE_HYGIENE_CONTRACT
|
|
7556
|
+
})
|
|
7557
|
+
};
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
const transcriptsDir = join27(input.vault, "raw", "transcripts");
|
|
7160
7561
|
try {
|
|
7161
7562
|
await mkdir6(transcriptsDir, { recursive: true });
|
|
7162
7563
|
} catch {
|
|
@@ -7168,7 +7569,7 @@ async function runObserve(input) {
|
|
|
7168
7569
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7169
7570
|
const slug = slugify(input.text);
|
|
7170
7571
|
const fileName = `${today}-observation-${slug}.md`;
|
|
7171
|
-
const filePath =
|
|
7572
|
+
const filePath = join27(transcriptsDir, fileName);
|
|
7172
7573
|
const body = `
|
|
7173
7574
|
${input.text.trim()}
|
|
7174
7575
|
`;
|
|
@@ -7210,7 +7611,7 @@ ${input.text.trim()}
|
|
|
7210
7611
|
// src/commands/memory.ts
|
|
7211
7612
|
import { createHash as createHash7 } from "crypto";
|
|
7212
7613
|
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
|
|
7614
|
+
import { basename as basename2, extname, join as join28, relative as relative4, sep as sep3 } from "path";
|
|
7214
7615
|
|
|
7215
7616
|
// src/utils/memory-authority.ts
|
|
7216
7617
|
var TIER_RANK = {
|
|
@@ -7329,8 +7730,8 @@ async function runMemoryIndex(input) {
|
|
|
7329
7730
|
}
|
|
7330
7731
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
7331
7732
|
const relCachePath = memoryCacheRelPath(input.project);
|
|
7332
|
-
const absCachePath =
|
|
7333
|
-
await mkdir7(
|
|
7733
|
+
const absCachePath = join28(input.vault, relCachePath);
|
|
7734
|
+
await mkdir7(join28(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
|
|
7334
7735
|
await writeFile5(absCachePath, `${JSON.stringify({
|
|
7335
7736
|
generated_at: generatedAt,
|
|
7336
7737
|
project: input.project,
|
|
@@ -7522,7 +7923,7 @@ async function buildMemoryIndexState(pages, project) {
|
|
|
7522
7923
|
}
|
|
7523
7924
|
async function checkMemoryIndex(vault, project, current) {
|
|
7524
7925
|
const relCachePath = memoryCacheRelPath(project);
|
|
7525
|
-
const cacheText = await readIfExists2(
|
|
7926
|
+
const cacheText = await readIfExists2(join28(vault, relCachePath));
|
|
7526
7927
|
if (!cacheText) {
|
|
7527
7928
|
return {
|
|
7528
7929
|
ok: true,
|
|
@@ -7931,7 +8332,7 @@ async function walkImportFiles(dir, out) {
|
|
|
7931
8332
|
const entries = await readdir5(dir, { withFileTypes: true });
|
|
7932
8333
|
for (const entry of entries) {
|
|
7933
8334
|
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
7934
|
-
const path =
|
|
8335
|
+
const path = join28(dir, entry.name);
|
|
7935
8336
|
if (entry.isDirectory()) {
|
|
7936
8337
|
await walkImportFiles(path, out);
|
|
7937
8338
|
} else if (entry.isFile() && isImportCandidate(path)) {
|
|
@@ -7981,7 +8382,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
|
|
|
7981
8382
|
const redacted = redactSensitiveContent(extracted, { file });
|
|
7982
8383
|
const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
|
|
7983
8384
|
const sourceSlug = slugify2(basename2(file, extname(file)));
|
|
7984
|
-
const relSource =
|
|
8385
|
+
const relSource = relative4(sourceRoot, file).split(sep3).join("/");
|
|
7985
8386
|
const entry = {
|
|
7986
8387
|
...baseEntry,
|
|
7987
8388
|
status: "ready",
|
|
@@ -7998,8 +8399,8 @@ async function writeImportCapture(vault, entry, today) {
|
|
|
7998
8399
|
const content = hiddenString(entry, "__content");
|
|
7999
8400
|
const project = hiddenString(entry, "__project");
|
|
8000
8401
|
const relPath = await availableImportPath(vault, entry.proposed_path);
|
|
8001
|
-
const absPath =
|
|
8002
|
-
await mkdir7(
|
|
8402
|
+
const absPath = join28(vault, relPath);
|
|
8403
|
+
await mkdir7(join28(vault, "raw", "transcripts"), { recursive: true });
|
|
8003
8404
|
await writeFile5(absPath, renderImportCapture(entry, content, project, today), "utf8");
|
|
8004
8405
|
const validation = await runValidate({ file: absPath });
|
|
8005
8406
|
return {
|
|
@@ -8015,7 +8416,7 @@ async function availableImportPath(vault, proposed) {
|
|
|
8015
8416
|
const stem = proposed.slice(0, -ext.length);
|
|
8016
8417
|
let candidate = proposed;
|
|
8017
8418
|
let i = 2;
|
|
8018
|
-
while (await readIfExists2(
|
|
8419
|
+
while (await readIfExists2(join28(vault, candidate))) {
|
|
8019
8420
|
candidate = `${stem}-${i}${ext}`;
|
|
8020
8421
|
i++;
|
|
8021
8422
|
}
|
|
@@ -8200,10 +8601,10 @@ function memoryCacheRelPath(project) {
|
|
|
8200
8601
|
}
|
|
8201
8602
|
async function readMemoryCache(vault, project) {
|
|
8202
8603
|
if (project) {
|
|
8203
|
-
const projectCache = await readIfExists2(
|
|
8604
|
+
const projectCache = await readIfExists2(join28(vault, memoryCacheRelPath(project)));
|
|
8204
8605
|
if (projectCache) return projectCache;
|
|
8205
8606
|
}
|
|
8206
|
-
return readIfExists2(
|
|
8607
|
+
return readIfExists2(join28(vault, ".skillwiki", "memory-topics.json"));
|
|
8207
8608
|
}
|
|
8208
8609
|
function dedupePages(pages) {
|
|
8209
8610
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8295,7 +8696,7 @@ function slugify2(value) {
|
|
|
8295
8696
|
|
|
8296
8697
|
// src/commands/query.ts
|
|
8297
8698
|
import { readFile as readFile18, stat as stat6 } from "fs/promises";
|
|
8298
|
-
import { join as
|
|
8699
|
+
import { join as join29 } from "path";
|
|
8299
8700
|
var W_KEYWORD = 2;
|
|
8300
8701
|
var W_SOURCE_OVERLAP = 4;
|
|
8301
8702
|
var W_WIKILINK = 3;
|
|
@@ -8416,7 +8817,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
8416
8817
|
return score;
|
|
8417
8818
|
}
|
|
8418
8819
|
async function loadOrBuildGraph(vault) {
|
|
8419
|
-
const graphPath =
|
|
8820
|
+
const graphPath = join29(vault, ".skillwiki", "graph.json");
|
|
8420
8821
|
let needsBuild = false;
|
|
8421
8822
|
try {
|
|
8422
8823
|
const fileStat = await stat6(graphPath);
|
|
@@ -8445,7 +8846,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
8445
8846
|
import { z } from "zod";
|
|
8446
8847
|
|
|
8447
8848
|
// src/mcp/vault-resolve.ts
|
|
8448
|
-
import { join as
|
|
8849
|
+
import { join as join30, resolve as resolve7 } from "path";
|
|
8449
8850
|
|
|
8450
8851
|
// src/mcp/allowlist.ts
|
|
8451
8852
|
import { resolve as resolve6, sep as sep4 } from "path";
|
|
@@ -8507,7 +8908,7 @@ async function resolveMcpVault(input) {
|
|
|
8507
8908
|
return ok({ vault: vaultPath, source });
|
|
8508
8909
|
}
|
|
8509
8910
|
function defaultGraphOut(vault) {
|
|
8510
|
-
return
|
|
8911
|
+
return join30(vault, ".skillwiki", "graph.json");
|
|
8511
8912
|
}
|
|
8512
8913
|
|
|
8513
8914
|
// src/mcp/result-format.ts
|
|
@@ -8524,7 +8925,7 @@ function formatToolResult(payload) {
|
|
|
8524
8925
|
// src/mcp/audit-log.ts
|
|
8525
8926
|
import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
|
|
8526
8927
|
import { homedir } from "os";
|
|
8527
|
-
import { join as
|
|
8928
|
+
import { join as join31 } from "path";
|
|
8528
8929
|
function auditEnabled() {
|
|
8529
8930
|
const v = process.env.SKILLWIKI_MCP_AUDIT;
|
|
8530
8931
|
if (v === "0" || v === "false") return false;
|
|
@@ -8536,7 +8937,7 @@ function auditSink() {
|
|
|
8536
8937
|
function auditFilePath() {
|
|
8537
8938
|
const custom = process.env.SKILLWIKI_MCP_AUDIT_FILE;
|
|
8538
8939
|
if (custom && custom.length > 0) return custom;
|
|
8539
|
-
return
|
|
8940
|
+
return join31(homedir(), ".skillwiki", "mcp-audit.jsonl");
|
|
8540
8941
|
}
|
|
8541
8942
|
function auditMcpToolCall(entry) {
|
|
8542
8943
|
if (!auditEnabled()) return;
|
|
@@ -8546,7 +8947,7 @@ function auditMcpToolCall(entry) {
|
|
|
8546
8947
|
return;
|
|
8547
8948
|
}
|
|
8548
8949
|
const path = auditFilePath();
|
|
8549
|
-
mkdirSync5(
|
|
8950
|
+
mkdirSync5(join31(path, ".."), { recursive: true });
|
|
8550
8951
|
appendFileSync(path, line, "utf8");
|
|
8551
8952
|
}
|
|
8552
8953
|
async function runMcpToolHandler(tool, input, fn) {
|
|
@@ -8767,7 +9168,7 @@ function registerMcpMutatingTools(server) {
|
|
|
8767
9168
|
|
|
8768
9169
|
// src/mcp/resources.ts
|
|
8769
9170
|
import { readFile as readFile20 } from "fs/promises";
|
|
8770
|
-
import { join as
|
|
9171
|
+
import { join as join33 } from "path";
|
|
8771
9172
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8772
9173
|
|
|
8773
9174
|
// src/mcp/lint-bucket.ts
|
|
@@ -8896,8 +9297,8 @@ async function fetchQueryPreview(input) {
|
|
|
8896
9297
|
|
|
8897
9298
|
// src/mcp/graph-html.ts
|
|
8898
9299
|
import { readFile as readFile19 } from "fs/promises";
|
|
8899
|
-
import { join as
|
|
8900
|
-
import { existsSync as
|
|
9300
|
+
import { join as join32 } from "path";
|
|
9301
|
+
import { existsSync as existsSync16 } from "fs";
|
|
8901
9302
|
var TYPE_COLORS = {
|
|
8902
9303
|
entities: "#e74c3c",
|
|
8903
9304
|
concepts: "#27ae60",
|
|
@@ -8965,9 +9366,9 @@ ${nodeSvg}
|
|
|
8965
9366
|
return { html, node_count: nodes.length, edge_count: edges.length, truncated };
|
|
8966
9367
|
}
|
|
8967
9368
|
async function fetchGraphHtmlReport(input) {
|
|
8968
|
-
const graphPath = input.graphPath ??
|
|
9369
|
+
const graphPath = input.graphPath ?? join32(input.vault, ".skillwiki", "graph.json");
|
|
8969
9370
|
const maxNodes = Math.min(Math.max(10, input.maxNodes ?? 120), 500);
|
|
8970
|
-
if (!
|
|
9371
|
+
if (!existsSync16(graphPath)) {
|
|
8971
9372
|
return {
|
|
8972
9373
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
8973
9374
|
result: err("GRAPH_MISSING", { path: graphPath, hint: "Run skillwiki.graph_build first." })
|
|
@@ -9039,7 +9440,7 @@ async function fetchStaleSummary(input) {
|
|
|
9039
9440
|
|
|
9040
9441
|
// src/mcp/resources.ts
|
|
9041
9442
|
async function readVaultFile(vault, rel) {
|
|
9042
|
-
return readFile20(
|
|
9443
|
+
return readFile20(join33(vault, rel), "utf8");
|
|
9043
9444
|
}
|
|
9044
9445
|
async function tailLines(text, lines) {
|
|
9045
9446
|
const parts = text.split(/\r?\n/);
|
|
@@ -9125,7 +9526,7 @@ function registerMcpResources(server) {
|
|
|
9125
9526
|
if (!v.ok) {
|
|
9126
9527
|
return { contents: [{ uri: uri.href, mimeType: "text/plain", text: JSON.stringify(v) }] };
|
|
9127
9528
|
}
|
|
9128
|
-
const path =
|
|
9529
|
+
const path = join33(v.data.vault, ".skillwiki", "graph.json");
|
|
9129
9530
|
try {
|
|
9130
9531
|
const raw = await readFile20(path, "utf8");
|
|
9131
9532
|
const graph = JSON.parse(raw);
|
|
@@ -9500,6 +9901,13 @@ export {
|
|
|
9500
9901
|
evaluateSatelliteRunHealth,
|
|
9501
9902
|
runDoctor,
|
|
9502
9903
|
readCliPackageJson,
|
|
9904
|
+
DEFAULT_DIRTY_VOLUME_THRESHOLD,
|
|
9905
|
+
DEFAULT_CAPTURE_BUDGET,
|
|
9906
|
+
DEFAULT_NO_DECISION_STREAK,
|
|
9907
|
+
GateError,
|
|
9908
|
+
evaluateDirtyVolumeGate,
|
|
9909
|
+
CAPTURE_HYGIENE_CONTRACT,
|
|
9910
|
+
runWritePreflight,
|
|
9503
9911
|
runObserve,
|
|
9504
9912
|
memoryAuthorityTiersRank,
|
|
9505
9913
|
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-ZFEZZQFM.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.10",
|
|
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": {
|