skillwiki 0.10.111 → 0.10.113

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.
Files changed (44) hide show
  1. package/dist/{chunk-I6IRJCUJ.js → chunk-3TKNAOJH.js} +2 -2
  2. package/dist/chunk-5D5HFQ76.js +111 -0
  3. package/dist/{chunk-OJJBZDPR.js → chunk-5RHR4FEK.js} +183 -211
  4. package/dist/{chunk-GQWCTJTU.js → chunk-6VUK2FTX.js} +1 -1
  5. package/dist/{chunk-JQV5KNPT.js → chunk-CBT35FAW.js} +3 -3
  6. package/dist/{chunk-HBJLFPQP.js → chunk-FQERKMD7.js} +458 -18
  7. package/dist/{chunk-PYPPG7RF.js → chunk-IJKJGQKL.js} +1 -1
  8. package/dist/{chunk-MRPJ7X7W.js → chunk-IJWZDQDH.js} +3 -3
  9. package/dist/{chunk-X5GL2FCU.js → chunk-JL2SCLXT.js} +1 -1
  10. package/dist/chunk-K3M6OSUW.js +77 -0
  11. package/dist/{chunk-5IBLXGLY.js → chunk-K3YZESND.js} +2 -2
  12. package/dist/chunk-OGEAFXON.js +60 -0
  13. package/dist/{chunk-6OQLBNKR.js → chunk-OWWWO2XS.js} +29 -4
  14. package/dist/{chunk-KWEYA3OE.js → chunk-SEY4N4CT.js} +1 -1
  15. package/dist/{chunk-UIEQIJXP.js → chunk-VNSZCK5V.js} +47 -44
  16. package/dist/{chunk-TDBUJUYH.js → chunk-XJ5VEIH7.js} +42 -18
  17. package/dist/cli.js +586 -122
  18. package/dist/{index-projection-2K672CBO.js → index-projection-RQSOEDXM.js} +5 -5
  19. package/dist/{journal-QU5IDXQF.js → journal-V5XHBEI5.js} +3 -3
  20. package/dist/{managed-write-preflight-GRSHQYKC.js → managed-write-preflight-2BMHZWVJ.js} +7 -7
  21. package/dist/mcp-auth-env-QONKW4EO.js +25 -0
  22. package/dist/{migrate-PIB4QA54.js → migrate-SRRCXI2K.js} +6 -5
  23. package/dist/mirror-H5BTBAON.js +151 -0
  24. package/dist/runtime-LQZIG3XN.js +20 -0
  25. package/dist/skillwiki-mcp.js +14 -12
  26. package/dist/sources-E6KHDRDE.js +10 -0
  27. package/dist/{watcher-MHG46J3R.js → watcher-VJ3TOU6J.js} +20 -110
  28. package/package.json +1 -1
  29. package/skills/.claude-plugin/plugin.json +1 -1
  30. package/skills/.codex-plugin/plugin.json +1 -1
  31. package/skills/.cursor-plugin/plugin.json +1 -1
  32. package/skills/package.json +1 -1
  33. package/skills/skills/using-skillwiki/SKILL.md +1 -1
  34. package/skills/skills/wiki-ingest/SKILL.md +47 -0
  35. package/skills/skills/wiki-init/SKILL.md +3 -0
  36. package/skills/skills/wiki-lint/SKILL.md +7 -0
  37. package/skills/using-skillwiki/SKILL.md +1 -1
  38. package/skills/wiki-ingest/SKILL.md +47 -0
  39. package/skills/wiki-init/SKILL.md +3 -0
  40. package/skills/wiki-lint/SKILL.md +7 -0
  41. package/templates/launchd/com.karlchow.skillwiki-mirror-sse.plist +20 -0
  42. package/dist/chunk-I2J3IJB7.js +0 -54
  43. package/dist/runtime-GLMI5OCT.js +0 -15
  44. package/dist/sources-D2MGP246.js +0 -10
@@ -4,11 +4,11 @@ import {
4
4
  UNMANAGED_START,
5
5
  renderRootIndex,
6
6
  writeRootIndexProjection
7
- } from "./chunk-MRPJ7X7W.js";
8
- import "./chunk-I6IRJCUJ.js";
9
- import "./chunk-X5GL2FCU.js";
10
- import "./chunk-GQWCTJTU.js";
11
- import "./chunk-UIEQIJXP.js";
7
+ } from "./chunk-IJWZDQDH.js";
8
+ import "./chunk-3TKNAOJH.js";
9
+ import "./chunk-JL2SCLXT.js";
10
+ import "./chunk-6VUK2FTX.js";
11
+ import "./chunk-VNSZCK5V.js";
12
12
  export {
13
13
  UNMANAGED_END,
14
14
  UNMANAGED_START,
@@ -3,9 +3,9 @@ import {
3
3
  readJournal,
4
4
  upsertJournal,
5
5
  writeJournal
6
- } from "./chunk-KWEYA3OE.js";
7
- import "./chunk-GQWCTJTU.js";
8
- import "./chunk-UIEQIJXP.js";
6
+ } from "./chunk-SEY4N4CT.js";
7
+ import "./chunk-6VUK2FTX.js";
8
+ import "./chunk-VNSZCK5V.js";
9
9
  export {
10
10
  readJournal,
11
11
  upsertJournal,
@@ -6,13 +6,13 @@ import {
6
6
  runManagedWritePeerGate,
7
7
  runManagedWritePreflight,
8
8
  runManagedWriteTransaction
9
- } from "./chunk-HBJLFPQP.js";
10
- import "./chunk-5IBLXGLY.js";
11
- import "./chunk-I6IRJCUJ.js";
12
- import "./chunk-X5GL2FCU.js";
13
- import "./chunk-PYPPG7RF.js";
14
- import "./chunk-GQWCTJTU.js";
15
- import "./chunk-UIEQIJXP.js";
9
+ } from "./chunk-FQERKMD7.js";
10
+ import "./chunk-K3YZESND.js";
11
+ import "./chunk-3TKNAOJH.js";
12
+ import "./chunk-JL2SCLXT.js";
13
+ import "./chunk-6VUK2FTX.js";
14
+ import "./chunk-IJKJGQKL.js";
15
+ import "./chunk-VNSZCK5V.js";
16
16
  export {
17
17
  DEFAULT_MANAGED_WRITE_WAIT_MS,
18
18
  MANAGED_WRITE_POLL_INTERVAL_MS,
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MCP_HOST_ID_ENV,
4
+ MCP_TOKEN_ENV,
5
+ MCP_URL_ENV,
6
+ isMcpOnlyLeaf,
7
+ mcpAuthFromEnv,
8
+ mergeMcpAuthIntoEnv,
9
+ nonEmptyEnvValue,
10
+ redactMcpSecret,
11
+ resolveMcpAuthEnv
12
+ } from "./chunk-OGEAFXON.js";
13
+ import "./chunk-IJKJGQKL.js";
14
+ import "./chunk-VNSZCK5V.js";
15
+ export {
16
+ MCP_HOST_ID_ENV,
17
+ MCP_TOKEN_ENV,
18
+ MCP_URL_ENV,
19
+ isMcpOnlyLeaf,
20
+ mcpAuthFromEnv,
21
+ mergeMcpAuthIntoEnv,
22
+ nonEmptyEnvValue,
23
+ redactMcpSecret,
24
+ resolveMcpAuthEnv
25
+ };
@@ -6,11 +6,12 @@ import {
6
6
  refreshInstalledClipPlist,
7
7
  stableCliBinary,
8
8
  xml
9
- } from "./chunk-TDBUJUYH.js";
10
- import "./chunk-6OQLBNKR.js";
11
- import "./chunk-PYPPG7RF.js";
12
- import "./chunk-GQWCTJTU.js";
13
- import "./chunk-UIEQIJXP.js";
9
+ } from "./chunk-XJ5VEIH7.js";
10
+ import "./chunk-6VUK2FTX.js";
11
+ import "./chunk-OWWWO2XS.js";
12
+ import "./chunk-5D5HFQ76.js";
13
+ import "./chunk-IJKJGQKL.js";
14
+ import "./chunk-VNSZCK5V.js";
14
15
  export {
15
16
  CLIP_MIGRATION_HINT,
16
17
  migrateClipOutbox,
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ writeRegularExclusive
4
+ } from "./chunk-K3M6OSUW.js";
5
+ import {
6
+ parseDestination
7
+ } from "./chunk-ZRGZBAVS.js";
8
+ import "./chunk-QESNTTVY.js";
9
+
10
+ // src/clip-outbox/mirror.ts
11
+ import { mkdir, rmdir } from "fs/promises";
12
+ import { dirname, join } from "path";
13
+ import { GetObjectCommand } from "@aws-sdk/client-s3";
14
+ var ADMIT = /^raw\/(articles|assets)\/[A-Za-z0-9._/-]+$/;
15
+ function admitMirrorPath(path) {
16
+ return ADMIT.test(path) && !path.split("/").some((part) => part === ".." || part === "." || !part);
17
+ }
18
+ function orderMirrorPaths(paths) {
19
+ const unique = [...new Set(paths.filter(admitMirrorPath))];
20
+ return [...unique.filter((path) => path.startsWith("raw/assets/")), ...unique.filter((path) => path.startsWith("raw/articles/"))];
21
+ }
22
+ function eventsUrlFromMcp(mcpUrl) {
23
+ const url = new URL(mcpUrl);
24
+ url.pathname = "/events";
25
+ url.search = "";
26
+ url.hash = "";
27
+ return url.toString();
28
+ }
29
+ function wikiFetchPullLockDir(home) {
30
+ return join(home, "Library", "Caches", "vault-sync", "wiki-fetch", "pull.lock.d");
31
+ }
32
+ async function acquireWikiFetchPullLock(home) {
33
+ const lockDir = wikiFetchPullLockDir(home);
34
+ await mkdir(dirname(lockDir), { recursive: true });
35
+ try {
36
+ await mkdir(lockDir);
37
+ } catch (error) {
38
+ if (error.code === "EEXIST") return null;
39
+ throw error;
40
+ }
41
+ return async () => {
42
+ await rmdir(lockDir).catch(() => void 0);
43
+ };
44
+ }
45
+ function parseChangedSseBlock(block) {
46
+ let event = "message";
47
+ let data = "";
48
+ for (const line of block.split(/\r?\n/)) {
49
+ if (line.startsWith("event:")) event = line.slice(6).trim();
50
+ else if (line.startsWith("data:")) data += line.slice(5).trimStart();
51
+ }
52
+ if (event !== "changed" || !data) return null;
53
+ try {
54
+ const parsed = JSON.parse(data);
55
+ if (parsed.vault_id !== "central" || !Array.isArray(parsed.paths)) return null;
56
+ return { vault_id: "central", paths: parsed.paths.filter((path) => typeof path === "string") };
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+ async function applyMirrorPaths(opts) {
62
+ const ordered = orderMirrorPaths(opts.paths);
63
+ const release = await opts.lock();
64
+ if (!release) return { copied: [], skipped: ordered };
65
+ try {
66
+ const copied = [];
67
+ const skipped = [];
68
+ for (const rel of ordered) {
69
+ const body = await opts.getObject(rel);
70
+ if (!body) {
71
+ skipped.push(rel);
72
+ continue;
73
+ }
74
+ const result = await opts.write(rel, body);
75
+ if (result === "conflict") skipped.push(rel);
76
+ else copied.push(rel);
77
+ }
78
+ return { copied, skipped };
79
+ } finally {
80
+ await release();
81
+ }
82
+ }
83
+ function createS3GetObject(opts) {
84
+ const prefix = opts.prefix ?? "";
85
+ const resolveKey = (rel) => [prefix, rel].filter(Boolean).join("/");
86
+ return async (key) => {
87
+ if (!admitMirrorPath(key)) return null;
88
+ try {
89
+ const response = await opts.client.send(new GetObjectCommand({ Bucket: opts.bucket, Key: resolveKey(key) }));
90
+ if (!response.Body) return null;
91
+ const bytes = Buffer.from(await response.Body.transformToByteArray());
92
+ return bytes.length > opts.maxBytes ? null : bytes;
93
+ } catch (error) {
94
+ const status = error.$metadata?.httpStatusCode;
95
+ if (status === 404 || error.name === "NoSuchKey") return null;
96
+ throw error;
97
+ }
98
+ };
99
+ }
100
+ async function startMirrorListener(input) {
101
+ const fetchImpl = input.fetchImpl ?? fetch;
102
+ const write = (rel, body) => writeRegularExclusive(input.config.mirrorRoot, rel, body);
103
+ const lock = input.lock ?? (() => acquireWikiFetchPullLock(input.home));
104
+ let delayMs = 1e3;
105
+ while (!input.signal.aborted) {
106
+ try {
107
+ const response = await fetchImpl(input.eventsUrl, {
108
+ headers: { Authorization: `Bearer ${input.mcpToken}`, Accept: "text/event-stream" },
109
+ signal: input.signal
110
+ });
111
+ if (!response.ok || !response.body) throw new Error("sse unavailable");
112
+ delayMs = 1e3;
113
+ let buffer = "";
114
+ const reader = response.body.getReader();
115
+ const decoder = new TextDecoder();
116
+ while (!input.signal.aborted) {
117
+ const { done, value } = await reader.read();
118
+ if (done) break;
119
+ buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
120
+ let split;
121
+ while ((split = buffer.indexOf("\n\n")) >= 0) {
122
+ const block = buffer.slice(0, split);
123
+ buffer = buffer.slice(split + 2);
124
+ const changed = parseChangedSseBlock(block);
125
+ if (!changed) continue;
126
+ await applyMirrorPaths({ paths: changed.paths, getObject: input.getObject, write, lock });
127
+ }
128
+ }
129
+ } catch (error) {
130
+ if (input.signal.aborted) return;
131
+ if (error.name === "AbortError") return;
132
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
133
+ delayMs = Math.min(delayMs * 2, 3e4);
134
+ }
135
+ }
136
+ }
137
+ function parseDestinationForMirror(destination) {
138
+ return parseDestination(destination);
139
+ }
140
+ export {
141
+ acquireWikiFetchPullLock,
142
+ admitMirrorPath,
143
+ applyMirrorPaths,
144
+ createS3GetObject,
145
+ eventsUrlFromMcp,
146
+ orderMirrorPaths,
147
+ parseChangedSseBlock,
148
+ parseDestinationForMirror,
149
+ startMirrorListener,
150
+ wikiFetchPullLockDir
151
+ };
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertClipS3Endpoint,
4
+ conditionalCreateProven,
5
+ evidencePath,
6
+ isRfc1918OrLoopback,
7
+ loadClipCredentials,
8
+ validateRuntime
9
+ } from "./chunk-OWWWO2XS.js";
10
+ import "./chunk-5D5HFQ76.js";
11
+ import "./chunk-IJKJGQKL.js";
12
+ import "./chunk-VNSZCK5V.js";
13
+ export {
14
+ assertClipS3Endpoint,
15
+ conditionalCreateProven,
16
+ evidencePath,
17
+ isRfc1918OrLoopback,
18
+ loadClipCredentials,
19
+ validateRuntime
20
+ };
@@ -1,19 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runSkillwikiMcpStdio
4
- } from "./chunk-OJJBZDPR.js";
5
- import "./chunk-JQV5KNPT.js";
4
+ } from "./chunk-5RHR4FEK.js";
5
+ import "./chunk-CBT35FAW.js";
6
6
  import "./chunk-7I2TPIV5.js";
7
- import "./chunk-MRPJ7X7W.js";
8
- import "./chunk-HBJLFPQP.js";
9
- import "./chunk-5IBLXGLY.js";
10
- import "./chunk-I6IRJCUJ.js";
11
- import "./chunk-X5GL2FCU.js";
12
- import "./chunk-TDBUJUYH.js";
13
- import "./chunk-6OQLBNKR.js";
14
- import "./chunk-PYPPG7RF.js";
15
- import "./chunk-GQWCTJTU.js";
16
- import "./chunk-UIEQIJXP.js";
7
+ import "./chunk-IJWZDQDH.js";
8
+ import "./chunk-FQERKMD7.js";
9
+ import "./chunk-K3YZESND.js";
10
+ import "./chunk-3TKNAOJH.js";
11
+ import "./chunk-JL2SCLXT.js";
12
+ import "./chunk-OGEAFXON.js";
13
+ import "./chunk-XJ5VEIH7.js";
14
+ import "./chunk-6VUK2FTX.js";
15
+ import "./chunk-OWWWO2XS.js";
16
+ import "./chunk-5D5HFQ76.js";
17
+ import "./chunk-IJKJGQKL.js";
18
+ import "./chunk-VNSZCK5V.js";
17
19
 
18
20
  // src/mcp-entry.ts
19
21
  runSkillwikiMcpStdio().catch((error) => {
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runSourcesPending
4
+ } from "./chunk-CBT35FAW.js";
5
+ import "./chunk-K3YZESND.js";
6
+ import "./chunk-JL2SCLXT.js";
7
+ import "./chunk-VNSZCK5V.js";
8
+ export {
9
+ runSourcesPending
10
+ };
@@ -1,35 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- sha256Hex
4
- } from "./chunk-ZRGZBAVS.js";
3
+ readJournal,
4
+ upsertJournal,
5
+ writeJournal
6
+ } from "./chunk-SEY4N4CT.js";
5
7
  import {
6
8
  buildReadyHint
7
9
  } from "./chunk-OSL4J7GF.js";
8
10
  import {
9
- runFetchGuardSync
10
- } from "./chunk-I2J3IJB7.js";
11
- import "./chunk-QESNTTVY.js";
11
+ readRegular
12
+ } from "./chunk-K3M6OSUW.js";
12
13
  import {
13
- readJournal,
14
- upsertJournal,
15
- writeJournal
16
- } from "./chunk-KWEYA3OE.js";
17
- import "./chunk-GQWCTJTU.js";
14
+ sha256Hex
15
+ } from "./chunk-ZRGZBAVS.js";
16
+ import "./chunk-QESNTTVY.js";
17
+ import "./chunk-6VUK2FTX.js";
18
18
  import {
19
- ipv4InCidr,
20
- isBlockedHost
21
- } from "./chunk-UIEQIJXP.js";
19
+ assertPublicHttpsUrl
20
+ } from "./chunk-5D5HFQ76.js";
21
+ import "./chunk-VNSZCK5V.js";
22
22
 
23
23
  // src/clip-outbox/watcher.ts
24
24
  import { createRequire } from "module";
25
25
  import { randomBytes as randomBytes2 } from "crypto";
26
26
  import { mkdir as mkdir2, readdir, rmdir, unlink, writeFile } from "fs/promises";
27
- import { basename, join as join2, resolve as resolve3 } from "path";
27
+ import { basename, join as join2, resolve as resolve2 } from "path";
28
28
 
29
29
  // src/clip-outbox/seal.ts
30
30
  import { randomBytes } from "crypto";
31
- import { mkdir, open as open2 } from "fs/promises";
32
- import { dirname, join, relative as relative3 } from "path";
31
+ import { mkdir, open } from "fs/promises";
32
+ import { dirname, join, relative as relative2 } from "path";
33
33
  import yaml from "js-yaml";
34
34
 
35
35
  // src/clip-outbox/localize.ts
@@ -108,60 +108,6 @@ function localAssetRelPath(target) {
108
108
  return void 0;
109
109
  }
110
110
 
111
- // src/clip-outbox/ssrf.ts
112
- import { isIP } from "net";
113
- import { lookup as dnsLookup } from "dns/promises";
114
- var extraBlockedV4 = [
115
- ["0.0.0.0", 8],
116
- ["100.64.0.0", 10],
117
- ["192.0.2.0", 24],
118
- ["198.18.0.0", 15],
119
- ["198.51.100.0", 24],
120
- ["203.0.113.0", 24],
121
- ["224.0.0.0", 4],
122
- ["240.0.0.0", 4]
123
- ];
124
- function isBlockedAddress(address) {
125
- if (isBlockedHost(address)) return true;
126
- const version = isIP(address);
127
- if (version === 4) {
128
- return extraBlockedV4.some(([base, prefix]) => ipv4InCidr(address, base, prefix));
129
- }
130
- if (version === 6) {
131
- const lower = address.toLowerCase();
132
- return lower === "::" || lower === "::1" || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80:");
133
- }
134
- return false;
135
- }
136
- async function assertPublicHttpsUrl(url, lookup = defaultLookup) {
137
- const guard = runFetchGuardSync({ url });
138
- if (!guard.result.ok) {
139
- return { ok: false, reason: guard.result.error };
140
- }
141
- let parsed;
142
- try {
143
- parsed = new URL(url);
144
- } catch {
145
- return { ok: false, reason: "MALFORMED_URL" };
146
- }
147
- if (isBlockedAddress(parsed.hostname)) {
148
- return { ok: false, reason: "HOST_BLOCKED" };
149
- }
150
- try {
151
- const addresses = await lookup(parsed.hostname);
152
- if (addresses.length === 0) return { ok: false, reason: "DNS_EMPTY" };
153
- if (addresses.some(isBlockedAddress)) return { ok: false, reason: "HOST_BLOCKED" };
154
- } catch {
155
- return { ok: false, reason: "DNS_FAILED" };
156
- }
157
- return { ok: true, href: parsed.href };
158
- }
159
- async function defaultLookup(hostname) {
160
- if (isIP(hostname)) return [hostname];
161
- const v4 = await dnsLookup(hostname, { all: true, verbatim: true });
162
- return v4.map((row) => row.address);
163
- }
164
-
165
111
  // src/clip-outbox/localize.ts
166
112
  var LocalizeError = class extends Error {
167
113
  constructor(message, code = "LOCALIZE_HELD") {
@@ -279,42 +225,6 @@ async function downloadImage(href, config, fetchImpl, lookup) {
279
225
  throw new LocalizeError("too many redirects");
280
226
  }
281
227
 
282
- // src/clip-outbox/files.ts
283
- import { constants } from "fs";
284
- import { lstat, open, realpath } from "fs/promises";
285
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "path";
286
- async function readRegular(root, path, maxBytes) {
287
- const rel = relative2(root, path);
288
- if (!rel || rel.startsWith(`..${sep}`) || rel === ".." || isAbsolute2(rel)) throw new Error("unsafe input path");
289
- let current = resolve2(root);
290
- for (const part of ["", ...rel.split(sep)]) {
291
- current = resolve2(current, part);
292
- const st = await lstat(current);
293
- if (st.isSymbolicLink()) throw new Error("symlink input refused");
294
- if (current !== path && !st.isDirectory()) throw new Error("invalid input parent");
295
- }
296
- const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
297
- try {
298
- const before = await handle.stat();
299
- if (!before.isFile() || before.size > maxBytes) throw new Error("input type or size refused");
300
- const bytes = Buffer.alloc(before.size + 1);
301
- let bytesRead = 0;
302
- while (bytesRead < bytes.length) {
303
- const chunk = await handle.read(bytes, bytesRead, bytes.length - bytesRead, bytesRead);
304
- if (chunk.bytesRead === 0) break;
305
- bytesRead += chunk.bytesRead;
306
- }
307
- const after = await handle.stat();
308
- if (await realpath(path) !== path) throw new Error("input path changed during read");
309
- const leaf = await lstat(path);
310
- if (leaf.ino !== before.ino || leaf.dev !== before.dev) throw new Error("input identity changed during read");
311
- if (bytesRead !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs) throw new Error("input changed during read");
312
- return bytes.subarray(0, bytesRead);
313
- } finally {
314
- await handle.close();
315
- }
316
- }
317
-
318
228
  // src/clip-outbox/seal.ts
319
229
  var UnstableCapture = class extends Error {
320
230
  };
@@ -325,7 +235,7 @@ function snapshotPath(config, entry, key) {
325
235
  }
326
236
  async function durableFile(root, path, bytes) {
327
237
  await mkdir(dirname(path), { recursive: true, mode: 448 });
328
- const f = await open2(path, "wx", 384);
238
+ const f = await open(path, "wx", 384);
329
239
  try {
330
240
  await f.writeFile(bytes);
331
241
  await f.sync();
@@ -334,7 +244,7 @@ async function durableFile(root, path, bytes) {
334
244
  }
335
245
  let dir = dirname(path);
336
246
  while (true) {
337
- const handle = await open2(dir, "r");
247
+ const handle = await open(dir, "r");
338
248
  try {
339
249
  await handle.sync();
340
250
  } finally {
@@ -383,7 +293,7 @@ async function sealCapture(config, entry, deps = {}) {
383
293
  return { ...entry, sourceSha256: sha256Hex(article), assetKeys: [...objects.keys()].filter((k) => k !== entry.articleKey), sha256: Object.fromEntries([...objects].map(([k, b]) => [k, sha256Hex(b)])), sizes: Object.fromEntries([...objects].map(([k, b]) => [k, b.length])), receipts: {}, state: "sealed", reason: void 0, attempts: 0, retryAt: void 0, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
384
294
  }
385
295
  function admittedArticle(root, path) {
386
- const rel = relative3(root, path).replaceAll("\\", "/");
296
+ const rel = relative2(root, path).replaceAll("\\", "/");
387
297
  if (!rel.startsWith("raw/articles/") || !rel.endsWith(".md")) return false;
388
298
  return rel.split("/").every((part) => part !== "." && part !== ".." && !part.startsWith(".") && !/[\x00-\x1f]/.test(part) && !part.endsWith("~"));
389
299
  }
@@ -580,7 +490,7 @@ async function startClipWorker(config, deps) {
580
490
  requestScan();
581
491
  return;
582
492
  }
583
- const absolute = resolve3(path);
493
+ const absolute = resolve2(path);
584
494
  if (admittedArticle(config.root, absolute)) schedule(absolute);
585
495
  else if (absolute.startsWith(join2(config.root, "raw/assets") + "/")) {
586
496
  for (const entry of entries) if (!entry.sourceSha256 && entry.source) schedule(entry.source);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.111",
3
+ "version": "0.10.113",
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.111",
3
+ "version": "0.10.113",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.10.111",
3
+ "version": "0.10.113",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 22 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",
3
- "version": "0.10.111",
3
+ "version": "0.10.113",
4
4
  "description": "Project-aware Karpathy-style knowledge base: 22 prompt-only skills (wiki-*, proj-*, using-skillwiki, skillwiki-mcp, skillwiki-connect) backed by HTTP MCP wiki_capture.",
5
5
  "author": {
6
6
  "name": "karlorz"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.10.111",
3
+ "version": "0.10.113",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -183,7 +183,7 @@ If the vault has `.WIKI_GIT_FROZEN`:
183
183
  ## Skill Map
184
184
  | Skill | When to Invoke |
185
185
  |-------|----------------|
186
- | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows; `wiki_capture` is not clip ingress. |
186
+ | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows including the inbound `com.karlchow.skillwiki-mirror-sse` listener, which upgrades never auto-load; `wiki_capture` is not clip ingress. |
187
187
  | `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
188
188
  | `wiki-query` | Search typed knowledge by default; work-queue / doctor / MCP diagnose questions use `wiki_context` or `--scope work` / `--scope all`; explicitly requested fresh/raw evidence uses the separate pending channel |
189
189
  | `wiki-lint` | Vault health and lint checks; use `health` for whole-system reports and `lint --summary` for bounded lint buckets |
@@ -42,6 +42,53 @@ Run `skillwiki lang` at the start. Generate page-body prose, narrative sections,
42
42
  5. `skillwiki sources compile published <raw-path> [vault] --pages "<typed/a.md,typed/b.md>" --reason "<why>"` then `--write --approve <token>`
43
43
  6. Leave the review `open` unless the human resolves it with `skillwiki sources review <raw-path> --status accepted|needs-fix|dismissed --reason "<why>"`
44
44
  Goal, satellite, and headless sessions may list pending / compile status / reviews but must not claim or publish compile turns.
45
+ ## Output standard v2
46
+ `skillwiki ingest` stamps `output_standard: v2` on every new typed stub. v2 is an authoring contract. CLI lint reports advisory warnings on marked pages only. Publish, health, and lint exit stay unchanged by these checks.
47
+
48
+ ### Rung 1 — STE-80 (default)
49
+ STE is Simplified Technical English.
50
+ - Use at most 20 words for each instruction. Use at most 25 words for each description.
51
+ - One topic per paragraph. Each paragraph <= 6 sentences.
52
+ - One word, one meaning. Prefer simple verbs: use, start, stop, find, check.
53
+ - Keep technical terms (API, cache, Docker). Define each term once at first use.
54
+ - No hedges except in `## Uncertainty`. Never drop facts to shorten.
55
+ - Hedge list: might, may, perhaps, possibly, probably, likely, seems, appears to, arguably, somewhat, could be.
56
+ - Use `## Uncertainty` only for uncertainty the source itself reports. If the source reports none, omit the section or write "The source reports no open uncertainty."
57
+
58
+ ### Rung 2 — diagrams (default when content has structure)
59
+ Add one Mermaid diagram when the page has flow, components, timeline, hierarchy, or comparison. Keep a text summary beside the diagram. Directly after the diagram, add `**Least certain:**` and a bullet list of labels or edges.
60
+
61
+ The `**Least certain:**` bullets name diagram labels or edges. State why that part of the diagram is the least certain representation. Use one of these reasons: an interpretive grouping, a simplified step, an omitted condition, or a mapping the source does not state directly.
62
+
63
+ Never add doubt about a fact the source states as settled. Never turn a decision, constraint, or policy into an open question.
64
+
65
+ If a diagram has fewer than 3 labels and edges in total, list all of them. Otherwise list the 3 least-certain labels or edges.
66
+
67
+ A diagram must not present a shortened executable procedure. For safety or recovery flows, keep all required steps or point the node to the full text procedure.
68
+
69
+ ### Rung 3 — HTML (opt-in)
70
+ Set `html_render: true` by hand or with `skillwiki ingest --html`. Render outside the vault:
71
+
72
+ ```bash
73
+ skillwiki page render-html <page> [vault] --out <file>
74
+ ```
75
+
76
+ HTML is always opt-in. The renderer writes one self-contained file (TOC, tabs per H2, `<details>`, inline CSS and JS). Mermaid stays as readable source. No network fetch.
77
+
78
+ ### Rung 4 — video
79
+ Explainer video is out of scope. Use STE text plus a diagram.
80
+
81
+ ### Format table
82
+
83
+ | Content type | Format | Rung | Policy |
84
+ | --- | --- | --- | --- |
85
+ | Exact procedure (re-read later) | STE text | 1 | default |
86
+ | Relationships (structure / flow / hierarchy) | Mermaid diagram + text | 2 | default |
87
+ | Comparison / hands-on exploration | HTML interactive page | 3 | opt-in; otherwise STE + table or diagram |
88
+ | Concept from zero | Explainer video | 4 | out of scope; use STE + diagram |
89
+
90
+ Rules: formats stack; HTML is always opt-in; video is outside the current scope.
91
+
45
92
  ## Provenance defaults
46
93
  - Default `provenance: research`.
47
94
  - If cwd is inside `projects/{slug}/`, set `provenance: project` and add `provenance_projects: ["[[slug]]"]`.
@@ -41,6 +41,9 @@ None for the first run.
41
41
  worker localizes required v1 HTTPS images before freeze, writes
42
42
  `![[raw/assets/...]]` embeds, and uploads `raw/assets/<capture-id>/note.md`.
43
43
  `wiki_capture` is not clip ingress. Never use `.txt` as the only index.
44
+ The inbound production SSE listener is a separate attended
45
+ `skillwiki clip-outbox install-or-migrate --load-mirror` job; package
46
+ upgrade never auto-loads it and never watches `~/wiki` as clip input.
44
47
  8. **Suggest first sources.** Propose 3–5 initial sources (URLs, papers, articles) appropriate to the domain. Prompt the user to provide the first one to ingest, then hand off to wiki-ingest.
45
48
 
46
49
  ## Stop conditions
@@ -19,6 +19,13 @@ Standard four reads.
19
19
  6. If `log_rotate_needed` is present and the user consents, run `skillwiki log-rotate <vault> --apply`. Otherwise leave alone.
20
20
  7. **Post-migration verification**: If the user recently migrated **vault content** (e.g., moved entity/concept pages to another vault), re-run `skillwiki lint <vault> --summary` and verify that broken_wikilinks count decreased. Remaining broken links for migrated content indicate pages still referencing the moved files — these should be cleaned up (remove citations or migrate the referencing pages too). This step is content moves only — not HTTP MCP client/plugin upgrades (those use `skillwiki doctor --check-mcp`).
21
21
  8. Append a `log.md` entry summarizing lint counts only when the user asked to record the maintenance result. Do not log routine `health` reports by default.
22
+ ## Output standard v2 WARN buckets
23
+ These four buckets apply only to typed pages whose frontmatter has `output_standard: v2`. They are advisory WARN findings. They do not change the lint exit code, publish, health exit, or release CI on content.
24
+ - `ste_sentence_length`: prose sentences > 25 words; ordered-list items and list items under Steps/Procedure headings > 20 words.
25
+ - `ste_hedge_words`: whole-word hedges outside `## Uncertainty`, outside the `**Least certain:**` list, and outside code and blockquotes.
26
+ - `ste_diagram_uncertainty`: a Mermaid block with no `**Least certain:**` bullet list after it.
27
+ - `ste_comparison_visual`: a `type: comparison` page with neither a Markdown table nor a Mermaid block.
28
+ Drill with `skillwiki lint <vault> --only <bucket>`.
22
29
  ## Stop conditions
23
30
  None — lint reports all findings even on per-page errors.
24
31
  ## Forbidden
@@ -183,7 +183,7 @@ If the vault has `.WIKI_GIT_FROZEN`:
183
183
  ## Skill Map
184
184
  | Skill | When to Invoke |
185
185
  |-------|----------------|
186
- | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows; `wiki_capture` is not clip ingress. |
186
+ | `wiki-init` | Bootstrap a vault and install `_Templates/web-clipper/llm-wiki-clippings.json` plus import guidance. File-level clips use a dedicated `~/Obsidian/clip-outbox` vault, not `~/wiki`; `skillwiki doctor` reports clip-channel rows including the inbound `com.karlchow.skillwiki-mirror-sse` listener, which upgrades never auto-load; `wiki_capture` is not clip ingress. |
187
187
  | `wiki-ingest` | Convert URLs, files, or pasted text into typed-knowledge pages |
188
188
  | `wiki-query` | Search typed knowledge by default; work-queue / doctor / MCP diagnose questions use `wiki_context` or `--scope work` / `--scope all`; explicitly requested fresh/raw evidence uses the separate pending channel |
189
189
  | `wiki-lint` | Vault health and lint checks; use `health` for whole-system reports and `lint --summary` for bounded lint buckets |