skillwiki 0.9.62 → 0.10.0
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.
- package/dist/chunk-5TBIMLTZ.js +343 -0
- package/dist/chunk-C5OLZRRM.js +357 -0
- package/dist/{chunk-2PENIQ3A.js → chunk-DR7KFHNH.js} +793 -1983
- package/dist/chunk-IZABIE44.js +647 -0
- package/dist/chunk-S5ABQCXQ.js +580 -0
- package/dist/cli.js +1820 -665
- package/dist/index-projection-ERFX76U5.js +10 -0
- package/dist/managed-write-preflight-SILEUQEV.js +11 -0
- package/dist/skillwiki-mcp.js +4 -1
- package/dist/vault-sync/scripts/lib/conflict-markers.sh +69 -0
- package/dist/vault-sync/scripts/lib/delete-intent.sh +74 -0
- package/dist/vault-sync/scripts/lib/fleet.sh +103 -0
- package/dist/vault-sync/scripts/lib/git-case.sh +71 -0
- package/dist/vault-sync/scripts/lib/git-materialization.sh +264 -0
- package/dist/vault-sync/scripts/lib/git-operation-journal.sh +469 -0
- package/dist/vault-sync/scripts/lib/git-rebase-state.sh +180 -0
- package/dist/vault-sync/scripts/lib/lockfile.sh +70 -0
- package/dist/vault-sync/scripts/lib/managed-write-lock.sh +80 -0
- package/dist/vault-sync/scripts/lib/platform.sh +184 -0
- package/dist/vault-sync/scripts/lib/runtime-manifest.sh +223 -0
- package/dist/vault-sync/scripts/wiki-fetch-notify.sh +207 -0
- package/dist/vault-sync/scripts/wiki-fuse-refresh.sh +405 -0
- package/dist/vault-sync/scripts/wiki-pull-with-auto-resolve.sh +631 -0
- package/dist/vault-sync/scripts/wiki-push.sh +364 -0
- package/dist/vault-sync/scripts/wiki-snapshot.sh +587 -0
- package/package.json +2 -2
- package/skills/.claude-plugin/plugin.json +2 -2
- package/skills/.codex-plugin/plugin.json +3 -3
- package/skills/README.md +13 -0
- package/skills/package.json +1 -1
- package/skills/proj-work/SKILL.md +3 -0
- package/skills/skills/proj-work/SKILL.md +3 -0
- package/skills/skills/using-skillwiki/SKILL.md +29 -2
- package/skills/skills/wiki-archive/SKILL.md +18 -4
- package/skills/skills/wiki-crystallize/SKILL.md +3 -0
- package/skills/skills/wiki-remove/SKILL.md +96 -0
- package/skills/using-skillwiki/SKILL.md +29 -2
- package/skills/wiki-archive/SKILL.md +18 -4
- package/skills/wiki-crystallize/SKILL.md +3 -0
- package/skills/wiki-remove/SKILL.md +96 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadFleetManifestAndHost
|
|
4
|
+
} from "./chunk-S5ABQCXQ.js";
|
|
5
|
+
import {
|
|
6
|
+
ExitCode,
|
|
7
|
+
err,
|
|
8
|
+
ok
|
|
9
|
+
} from "./chunk-C5OLZRRM.js";
|
|
10
|
+
|
|
11
|
+
// src/utils/managed-write-preflight.ts
|
|
12
|
+
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
13
|
+
import { join as join3 } from "path";
|
|
14
|
+
|
|
15
|
+
// src/utils/git.ts
|
|
16
|
+
import { execFileSync } from "child_process";
|
|
17
|
+
function git(cwd, args) {
|
|
18
|
+
try {
|
|
19
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
20
|
+
} catch {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function gitStrict(cwd, args) {
|
|
25
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/utils/managed-write-lock.ts
|
|
29
|
+
import { randomBytes } from "crypto";
|
|
30
|
+
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
31
|
+
import { dirname, join } from "path";
|
|
32
|
+
function managedWriteLockPath(vault) {
|
|
33
|
+
const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/managed-write.lock"]);
|
|
34
|
+
if (gitPath) return gitPath.startsWith("/") ? gitPath : join(vault, gitPath);
|
|
35
|
+
return join(vault, ".skillwiki", "managed-write.lock");
|
|
36
|
+
}
|
|
37
|
+
function acquireManagedWriteLock(vault, command) {
|
|
38
|
+
const path = managedWriteLockPath(vault);
|
|
39
|
+
const ownerToken = randomBytes(16).toString("hex");
|
|
40
|
+
const acquired = (/* @__PURE__ */ new Date()).toISOString();
|
|
41
|
+
try {
|
|
42
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
43
|
+
writeFileSync(
|
|
44
|
+
path,
|
|
45
|
+
`${JSON.stringify({ pid: process.pid, owner_token: ownerToken, acquired, command })}
|
|
46
|
+
`,
|
|
47
|
+
{ flag: "wx" }
|
|
48
|
+
);
|
|
49
|
+
return ok({ vault, path, ownerToken, acquired });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.code === "EEXIST") return err("SYNC_LOCK_HELD", { path });
|
|
52
|
+
return err("WRITE_FAILED", { path, message: String(error) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function releaseManagedWriteLock(handle) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(readFileSync(handle.path, "utf8"));
|
|
58
|
+
if (parsed.owner_token !== handle.ownerToken || parsed.acquired !== handle.acquired) {
|
|
59
|
+
return err("SYNC_LOCK_HELD", {
|
|
60
|
+
path: handle.path,
|
|
61
|
+
message: "managed-write lock ownership changed"
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
unlinkSync(handle.path);
|
|
65
|
+
return ok({ released: true });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return err("WRITE_FAILED", { path: handle.path, message: String(error) });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/utils/vault-sync-helper.ts
|
|
72
|
+
import { spawnSync } from "child_process";
|
|
73
|
+
import { existsSync } from "fs";
|
|
74
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
75
|
+
import { fileURLToPath } from "url";
|
|
76
|
+
function candidateHelperPaths(input) {
|
|
77
|
+
const env = input.env ?? process.env;
|
|
78
|
+
const paths = [];
|
|
79
|
+
if (input.helperPath) paths.push(input.helperPath);
|
|
80
|
+
if (env.SKILLWIKI_VAULT_SYNC_PULL_HELPER) paths.push(env.SKILLWIKI_VAULT_SYNC_PULL_HELPER);
|
|
81
|
+
try {
|
|
82
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
83
|
+
paths.push(join2(here, "..", "vault-sync", "scripts", "wiki-pull-with-auto-resolve.sh"));
|
|
84
|
+
paths.push(join2(here, "..", "..", "vault-sync", "scripts", "wiki-pull-with-auto-resolve.sh"));
|
|
85
|
+
paths.push(join2(here, "..", "..", "..", "vault-sync", "scripts", "wiki-pull-with-auto-resolve.sh"));
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
return paths;
|
|
89
|
+
}
|
|
90
|
+
function resolveVaultSyncPullHelper(input) {
|
|
91
|
+
for (const p of candidateHelperPaths(input)) {
|
|
92
|
+
if (p && existsSync(p)) return p;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
async function runVaultSyncPullHelper(input) {
|
|
97
|
+
const helperPath = resolveVaultSyncPullHelper(input);
|
|
98
|
+
if (!helperPath) {
|
|
99
|
+
return err("GIT_PULL_FAILED", { message: "canonical vault-sync pull helper not found" });
|
|
100
|
+
}
|
|
101
|
+
const remote = input.remote ?? "origin";
|
|
102
|
+
const branch = input.branch ?? "main";
|
|
103
|
+
const beforeOid = git(input.vault, ["rev-parse", "HEAD"]);
|
|
104
|
+
if (!beforeOid) {
|
|
105
|
+
return err("GIT_PULL_FAILED", { message: "could not read HEAD before pull" });
|
|
106
|
+
}
|
|
107
|
+
const env = {
|
|
108
|
+
...process.env,
|
|
109
|
+
...input.env ?? {},
|
|
110
|
+
WIKI_DIR: input.vault
|
|
111
|
+
};
|
|
112
|
+
if (input.lockToken) {
|
|
113
|
+
env.VAULT_SYNC_MANAGED_LOCK_TOKEN = input.lockToken;
|
|
114
|
+
}
|
|
115
|
+
const result = spawnSync("bash", [helperPath, remote, branch], {
|
|
116
|
+
env,
|
|
117
|
+
encoding: "utf8",
|
|
118
|
+
cwd: input.vault
|
|
119
|
+
});
|
|
120
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
121
|
+
const status = result.status ?? 1;
|
|
122
|
+
if (status === 2) {
|
|
123
|
+
return err("PREFLIGHT_FAILED", { reason: "existing-handoff", output, helper_path: helperPath });
|
|
124
|
+
}
|
|
125
|
+
if (status !== 0) {
|
|
126
|
+
return err("GIT_PULL_FAILED", {
|
|
127
|
+
message: result.error ? String(result.error) : `helper exited ${status}`,
|
|
128
|
+
output,
|
|
129
|
+
helper_path: helperPath
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const afterOid = git(input.vault, ["rev-parse", "HEAD"]);
|
|
133
|
+
if (!afterOid) {
|
|
134
|
+
return err("GIT_PULL_FAILED", { message: "could not read HEAD after pull", helper_path: helperPath });
|
|
135
|
+
}
|
|
136
|
+
return ok({
|
|
137
|
+
before_oid: beforeOid,
|
|
138
|
+
after_oid: afterOid,
|
|
139
|
+
changed: beforeOid !== afterOid,
|
|
140
|
+
helper_path: helperPath
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/utils/managed-write-preflight.ts
|
|
145
|
+
var DEFAULT_DEPS = {
|
|
146
|
+
converge: (input) => runVaultSyncPullHelper(input)
|
|
147
|
+
};
|
|
148
|
+
function journalDir(vault) {
|
|
149
|
+
const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/operations"]);
|
|
150
|
+
if (!gitPath) return null;
|
|
151
|
+
return gitPath.startsWith("/") ? gitPath : join3(vault, gitPath);
|
|
152
|
+
}
|
|
153
|
+
function findReviewRequiredOp(vault) {
|
|
154
|
+
const dir = journalDir(vault);
|
|
155
|
+
if (!dir) return void 0;
|
|
156
|
+
let files;
|
|
157
|
+
try {
|
|
158
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".env"));
|
|
159
|
+
} catch {
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
const currentGitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
let text;
|
|
165
|
+
try {
|
|
166
|
+
text = readFileSync2(join3(dir, file), "utf8");
|
|
167
|
+
} catch {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const fields = Object.fromEntries(
|
|
171
|
+
text.split("\n").filter((line) => line.includes("=")).map((line) => {
|
|
172
|
+
const i = line.indexOf("=");
|
|
173
|
+
return [line.slice(0, i), line.slice(i + 1)];
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
if (fields.phase !== "review-required" || fields.handoff !== "1") continue;
|
|
177
|
+
const journalGitDir = fields.worktree_git_dir ?? "";
|
|
178
|
+
if (!journalGitDir || !currentGitDir || journalGitDir === currentGitDir) {
|
|
179
|
+
return file.replace(/\.env$/, "");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
function preflightBlocker(vault) {
|
|
185
|
+
const unmergedRaw = git(vault, ["diff", "--name-only", "--diff-filter=U"]);
|
|
186
|
+
const unmerged = unmergedRaw ? unmergedRaw.split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
187
|
+
if (unmerged.length > 0) {
|
|
188
|
+
return {
|
|
189
|
+
reason: "unmerged-paths",
|
|
190
|
+
operation_id: findReviewRequiredOp(vault),
|
|
191
|
+
unmerged_paths: unmerged
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
195
|
+
if (gitDir) {
|
|
196
|
+
const markers = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"];
|
|
197
|
+
for (const m of markers) {
|
|
198
|
+
try {
|
|
199
|
+
readFileSync2(join3(gitDir, m));
|
|
200
|
+
return { reason: "git-operation-in-progress" };
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const op = findReviewRequiredOp(vault);
|
|
206
|
+
if (op) return { reason: "review-required", operation_id: op };
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
|
|
210
|
+
const vault = input.vault;
|
|
211
|
+
const blocker = preflightBlocker(vault);
|
|
212
|
+
if (blocker) {
|
|
213
|
+
return {
|
|
214
|
+
exitCode: ExitCode.PREFLIGHT_FAILED,
|
|
215
|
+
result: err("PREFLIGHT_FAILED", {
|
|
216
|
+
reason: blocker.reason,
|
|
217
|
+
operation_id: blocker.operation_id,
|
|
218
|
+
unmerged_paths: blocker.unmerged_paths
|
|
219
|
+
})
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const fleet = await loadFleetManifestAndHost({
|
|
223
|
+
vault,
|
|
224
|
+
hostId: input.hostId,
|
|
225
|
+
env: input.env,
|
|
226
|
+
home: input.home,
|
|
227
|
+
osHostname: input.osHostname
|
|
228
|
+
});
|
|
229
|
+
if (!fleet) {
|
|
230
|
+
const head = git(vault, ["rev-parse", "HEAD"]) || null;
|
|
231
|
+
return {
|
|
232
|
+
exitCode: ExitCode.OK,
|
|
233
|
+
result: ok({
|
|
234
|
+
mode: "standalone",
|
|
235
|
+
base_oid: head,
|
|
236
|
+
converged: false
|
|
237
|
+
})
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
if (fleet.identityStatus === "unknown" || fleet.identityStatus === "invalid" || !fleet.hostId) {
|
|
241
|
+
return {
|
|
242
|
+
exitCode: ExitCode.PREFLIGHT_FAILED,
|
|
243
|
+
result: err("PREFLIGHT_FAILED", {
|
|
244
|
+
reason: "fleet-identity-unresolved",
|
|
245
|
+
identity_status: fleet.identityStatus,
|
|
246
|
+
host_id: fleet.hostId
|
|
247
|
+
})
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const host = fleet.manifest.hosts[fleet.hostId];
|
|
251
|
+
if (!host) {
|
|
252
|
+
return {
|
|
253
|
+
exitCode: ExitCode.PREFLIGHT_FAILED,
|
|
254
|
+
result: err("PREFLIGHT_FAILED", { reason: "fleet-host-missing", host_id: fleet.hostId })
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const writesGithub = host.writes_to.includes("github");
|
|
258
|
+
if (!writesGithub) {
|
|
259
|
+
return {
|
|
260
|
+
exitCode: ExitCode.OK,
|
|
261
|
+
result: ok({
|
|
262
|
+
mode: "immutable-record",
|
|
263
|
+
host_id: fleet.hostId,
|
|
264
|
+
base_oid: null,
|
|
265
|
+
converged: false
|
|
266
|
+
})
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const converge = await deps.converge({
|
|
270
|
+
vault,
|
|
271
|
+
lockToken: input.lockToken,
|
|
272
|
+
env: input.env
|
|
273
|
+
});
|
|
274
|
+
if (!converge.ok) {
|
|
275
|
+
const exitCode = converge.error === "PREFLIGHT_FAILED" ? ExitCode.PREFLIGHT_FAILED : ExitCode.SYNC_PULL_FAILED;
|
|
276
|
+
return { exitCode, result: converge };
|
|
277
|
+
}
|
|
278
|
+
const baseOid = git(vault, ["rev-parse", "HEAD"]);
|
|
279
|
+
if (!baseOid) {
|
|
280
|
+
return {
|
|
281
|
+
exitCode: ExitCode.PREFLIGHT_FAILED,
|
|
282
|
+
result: err("PREFLIGHT_FAILED", { reason: "missing-head-after-converge" })
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
exitCode: ExitCode.OK,
|
|
287
|
+
result: ok({
|
|
288
|
+
mode: "git-writer",
|
|
289
|
+
host_id: fleet.hostId,
|
|
290
|
+
base_oid: baseOid,
|
|
291
|
+
converged: true,
|
|
292
|
+
helper_path: converge.data.helper_path
|
|
293
|
+
})
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
|
|
297
|
+
const lock = acquireManagedWriteLock(input.vault, input.command);
|
|
298
|
+
if (!lock.ok) {
|
|
299
|
+
return { exitCode: ExitCode.SYNC_LOCK_HELD, result: lock };
|
|
300
|
+
}
|
|
301
|
+
let handle = lock.data;
|
|
302
|
+
try {
|
|
303
|
+
const preflight = await runManagedWritePreflight(
|
|
304
|
+
{
|
|
305
|
+
vault: input.vault,
|
|
306
|
+
command: input.command,
|
|
307
|
+
hostId: input.hostId,
|
|
308
|
+
lockToken: handle.ownerToken,
|
|
309
|
+
env: input.env,
|
|
310
|
+
home: input.home,
|
|
311
|
+
osHostname: input.osHostname
|
|
312
|
+
},
|
|
313
|
+
deps
|
|
314
|
+
);
|
|
315
|
+
if (!preflight.result.ok) {
|
|
316
|
+
return { exitCode: preflight.exitCode, result: preflight.result };
|
|
317
|
+
}
|
|
318
|
+
const receipt = preflight.result.data;
|
|
319
|
+
if (receipt.mode === "immutable-record" && !input.allowImmutableRecord) {
|
|
320
|
+
return {
|
|
321
|
+
exitCode: ExitCode.PREFLIGHT_FAILED,
|
|
322
|
+
result: err("PREFLIGHT_FAILED", {
|
|
323
|
+
reason: "immutable-record-not-enabled",
|
|
324
|
+
message: "Release A rejects immutable-record mode; event mode arrives in Release B",
|
|
325
|
+
host_id: receipt.host_id
|
|
326
|
+
})
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return await input.mutate(receipt);
|
|
330
|
+
} finally {
|
|
331
|
+
releaseManagedWriteLock(handle);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export {
|
|
336
|
+
git,
|
|
337
|
+
gitStrict,
|
|
338
|
+
acquireManagedWriteLock,
|
|
339
|
+
releaseManagedWriteLock,
|
|
340
|
+
runVaultSyncPullHelper,
|
|
341
|
+
runManagedWritePreflight,
|
|
342
|
+
runManagedWriteTransaction
|
|
343
|
+
};
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../shared/src/exit-codes.ts
|
|
4
|
+
var ExitCode = {
|
|
5
|
+
OK: 0,
|
|
6
|
+
INTERNAL_ERROR: 1,
|
|
7
|
+
FILE_NOT_FOUND: 2,
|
|
8
|
+
MISSING_CLOSING_DELIMITER: 3,
|
|
9
|
+
SCHEME_REJECTED: 4,
|
|
10
|
+
HOST_BLOCKED: 5,
|
|
11
|
+
MALFORMED_URL: 6,
|
|
12
|
+
INVALID_FRONTMATTER: 7,
|
|
13
|
+
SCHEMA_NOT_DETECTED: 8,
|
|
14
|
+
VAULT_PATH_INVALID: 9,
|
|
15
|
+
WRITE_FAILED: 10,
|
|
16
|
+
UNRESOLVED_MARKERS: 11,
|
|
17
|
+
SOURCES_INCONSISTENT: 12,
|
|
18
|
+
PREFLIGHT_FAILED: 13,
|
|
19
|
+
ATOMIC_COPY_FAILED: 14,
|
|
20
|
+
INIT_TARGET_NOT_EMPTY: 15,
|
|
21
|
+
BROKEN_WIKILINKS: 16,
|
|
22
|
+
TAG_NOT_IN_TAXONOMY: 17,
|
|
23
|
+
INDEX_INCOMPLETE: 18,
|
|
24
|
+
STALE_PAGE: 19,
|
|
25
|
+
PAGE_TOO_LARGE: 20,
|
|
26
|
+
LOG_ROTATE_NEEDED: 21,
|
|
27
|
+
LINT_HAS_WARNINGS: 22,
|
|
28
|
+
LINT_HAS_ERRORS: 23,
|
|
29
|
+
ENV_WRITE_CONFLICT: 24,
|
|
30
|
+
NO_VAULT_CONFIGURED: 25,
|
|
31
|
+
INVALID_CONFIG_KEY: 26,
|
|
32
|
+
CONFIG_WRITE_FAILED: 27,
|
|
33
|
+
DOCTOR_HAS_WARNINGS: 28,
|
|
34
|
+
DOCTOR_HAS_ERRORS: 29,
|
|
35
|
+
ARCHIVE_TARGET_NOT_FOUND: 30,
|
|
36
|
+
ARCHIVE_ALREADY_ARCHIVED: 31,
|
|
37
|
+
DRIFT_DETECTED: 32,
|
|
38
|
+
RAW_DEDUP_DETECTED: 33,
|
|
39
|
+
MIGRATION_APPLIED: 34,
|
|
40
|
+
UNKNOWN_WIKI_PROFILE: 35,
|
|
41
|
+
DEDUP_APPLIED: 36,
|
|
42
|
+
PROJECT_NOT_FOUND: 37,
|
|
43
|
+
SYMLINK_FAILED: 38,
|
|
44
|
+
COMPOUND_PROMOTED: 39,
|
|
45
|
+
SKILL_VERSION_MISMATCH: 40,
|
|
46
|
+
INGEST_VALIDATION_FAILED: 41,
|
|
47
|
+
SYNC_PUSH_FAILED: 42,
|
|
48
|
+
SYNC_PULL_FAILED: 43,
|
|
49
|
+
BACKUP_SYNC_FAILED: 44,
|
|
50
|
+
BACKUP_RESTORE_CONFLICTS: 45,
|
|
51
|
+
USAGE: 46,
|
|
52
|
+
BODY_TRUNCATION_GUARD: 47,
|
|
53
|
+
SYNC_LOCK_HELD: 48,
|
|
54
|
+
LOG_APPEND_LOCK_HELD: 49,
|
|
55
|
+
FLEET_MANIFEST_INVALID: 50,
|
|
56
|
+
SENSITIVE_CONTENT_DETECTED: 51,
|
|
57
|
+
FLEET_SATELLITE_HEALTH_FAILED: 52,
|
|
58
|
+
PROTECTED_SNAPSHOTTER_WRITE_BLOCKED: 53
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ../shared/src/json-output.ts
|
|
62
|
+
function ok(data) {
|
|
63
|
+
return { ok: true, data };
|
|
64
|
+
}
|
|
65
|
+
function err(error, detail) {
|
|
66
|
+
return detail === void 0 ? { ok: false, error } : { ok: false, error, detail };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ../shared/src/schemas.ts
|
|
70
|
+
import { z } from "zod";
|
|
71
|
+
var isoDate = z.string().refine((s) => {
|
|
72
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return false;
|
|
73
|
+
const d = /* @__PURE__ */ new Date(s + "T00:00:00Z");
|
|
74
|
+
return !Number.isNaN(d.getTime()) && s === d.toISOString().slice(0, 10);
|
|
75
|
+
}, { message: "must be YYYY-MM-DD" });
|
|
76
|
+
var wikilink = z.string().regex(/^\[\[[^\[\]]+\]\]$/, 'must be "[[name]]"');
|
|
77
|
+
var TypedKnowledgeSchema = z.object({
|
|
78
|
+
title: z.string().min(1),
|
|
79
|
+
aliases: z.array(z.string()).optional(),
|
|
80
|
+
created: isoDate,
|
|
81
|
+
updated: isoDate,
|
|
82
|
+
type: z.enum(["entity", "concept", "comparison", "query"]),
|
|
83
|
+
tags: z.array(z.string()),
|
|
84
|
+
sources: z.array(z.string()).min(1),
|
|
85
|
+
confidence: z.enum(["high", "medium", "low"]).optional(),
|
|
86
|
+
contested: z.boolean().optional(),
|
|
87
|
+
contradictions: z.array(z.string()).optional(),
|
|
88
|
+
provenance: z.enum(["research", "project", "mixed"]).optional(),
|
|
89
|
+
provenance_projects: z.array(wikilink).optional(),
|
|
90
|
+
work_items: z.array(wikilink).optional(),
|
|
91
|
+
stale_ttl: z.number().int().positive().optional()
|
|
92
|
+
}).superRefine((v, ctx) => {
|
|
93
|
+
if (v.provenance && v.provenance !== "research" && (!v.provenance_projects || v.provenance_projects.length === 0)) {
|
|
94
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["provenance_projects"], message: "required when provenance != research" });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
var sha256Hex = z.string().regex(/^[0-9a-f]{64}$/);
|
|
98
|
+
var RawSourceSchema = z.object({
|
|
99
|
+
title: z.string().min(1).optional(),
|
|
100
|
+
source_url: z.string().nullable(),
|
|
101
|
+
created: isoDate.optional(),
|
|
102
|
+
ingested: isoDate,
|
|
103
|
+
ingested_by: z.enum(["wiki-ingest", "proj-work", "manual"]).optional(),
|
|
104
|
+
sha256: sha256Hex.optional(),
|
|
105
|
+
project: wikilink.optional(),
|
|
106
|
+
work_item: wikilink.optional(),
|
|
107
|
+
kind: z.enum(["postmortem", "session-log", "meeting-notes", "other", "idea", "bug", "task", "note"]).optional()
|
|
108
|
+
}).superRefine((v, ctx) => {
|
|
109
|
+
if (v.work_item !== void 0 && (v.project === void 0 || v.kind === void 0)) {
|
|
110
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "project and kind are required when work_item is set" });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
var WorkItemSchema = z.object({
|
|
114
|
+
title: z.string().min(1),
|
|
115
|
+
aliases: z.array(z.string()).optional(),
|
|
116
|
+
created: isoDate,
|
|
117
|
+
updated: isoDate,
|
|
118
|
+
started: isoDate,
|
|
119
|
+
completed: isoDate.optional(),
|
|
120
|
+
kind: z.enum(["feature", "issue", "refactor", "decision"]),
|
|
121
|
+
status: z.enum(["planned", "in-progress", "completed", "abandoned"]),
|
|
122
|
+
priority: z.enum(["high", "medium", "low"]),
|
|
123
|
+
project: wikilink,
|
|
124
|
+
owner: wikilink.optional(),
|
|
125
|
+
parent: wikilink.optional(),
|
|
126
|
+
related: z.array(wikilink).optional(),
|
|
127
|
+
sources: z.array(z.string()).optional()
|
|
128
|
+
}).superRefine((v, ctx) => {
|
|
129
|
+
if (v.status === "completed" && !v.completed) {
|
|
130
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["completed"], message: "required when status is completed" });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
var CompoundSchema = z.object({
|
|
134
|
+
title: z.string().min(1),
|
|
135
|
+
aliases: z.array(z.string()).optional(),
|
|
136
|
+
created: isoDate,
|
|
137
|
+
updated: isoDate,
|
|
138
|
+
type: z.enum(["lesson", "pattern", "antipattern", "gotcha"]),
|
|
139
|
+
tags: z.array(z.string()),
|
|
140
|
+
confidence: z.enum(["high", "medium", "low"]),
|
|
141
|
+
contradicts: z.array(z.string()).optional(),
|
|
142
|
+
project: wikilink,
|
|
143
|
+
work_items: z.array(wikilink).min(1),
|
|
144
|
+
promoted_to: wikilink.optional(),
|
|
145
|
+
cssclasses: z.array(z.string()).optional()
|
|
146
|
+
});
|
|
147
|
+
var sessionPinPath = z.string().regex(
|
|
148
|
+
/^(entities|concepts|comparisons|queries|meta)\/.+\.md$/,
|
|
149
|
+
"must reference a typed-knowledge markdown page"
|
|
150
|
+
);
|
|
151
|
+
var SessionPinSchema = z.object({
|
|
152
|
+
title: z.string().min(1),
|
|
153
|
+
path: sessionPinPath,
|
|
154
|
+
scope: z.enum(["global", "project"]),
|
|
155
|
+
project: wikilink.optional(),
|
|
156
|
+
summary: z.string().min(1).optional(),
|
|
157
|
+
updated: isoDate.optional()
|
|
158
|
+
}).superRefine((v, ctx) => {
|
|
159
|
+
if (v.scope === "project" && v.project === void 0) {
|
|
160
|
+
ctx.addIssue({
|
|
161
|
+
code: z.ZodIssueCode.custom,
|
|
162
|
+
path: ["project"],
|
|
163
|
+
message: "project is required when scope is project"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
var MetaSchema = z.object({
|
|
168
|
+
title: z.string().min(1),
|
|
169
|
+
aliases: z.array(z.string()).optional(),
|
|
170
|
+
created: isoDate,
|
|
171
|
+
updated: isoDate,
|
|
172
|
+
type: z.literal("meta"),
|
|
173
|
+
tags: z.array(z.string()),
|
|
174
|
+
confidence: z.enum(["high", "medium", "low"]).optional(),
|
|
175
|
+
provenance: z.enum(["research", "project", "mixed"]).optional(),
|
|
176
|
+
provenance_projects: z.array(wikilink).optional(),
|
|
177
|
+
generated_by: z.string().min(1).optional(),
|
|
178
|
+
generated_at: z.string().datetime().optional(),
|
|
179
|
+
generated_kind: z.enum(["session-brief"]).optional(),
|
|
180
|
+
meta_kind: z.enum(["session-pins"]).optional(),
|
|
181
|
+
stale_ttl: z.number().int().positive().optional(),
|
|
182
|
+
pins: z.array(SessionPinSchema).optional()
|
|
183
|
+
}).superRefine((v, ctx) => {
|
|
184
|
+
const isGeneratedSessionBrief = v.generated_kind === "session-brief";
|
|
185
|
+
const isSessionPins = v.meta_kind === "session-pins";
|
|
186
|
+
if (isSessionPins && (!v.pins || v.pins.length === 0)) {
|
|
187
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["pins"], message: "required when meta_kind is session-pins" });
|
|
188
|
+
}
|
|
189
|
+
if (!isGeneratedSessionBrief && !isSessionPins && (!v.provenance_projects || v.provenance_projects.length < 2)) {
|
|
190
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["provenance_projects"], message: "meta pages must reference \u22652 projects" });
|
|
191
|
+
}
|
|
192
|
+
if (v.provenance && v.provenance !== "research" && (!v.provenance_projects || v.provenance_projects.length === 0)) {
|
|
193
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["provenance_projects"], message: "required when provenance != research" });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
var hostId = z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, "must be a lowercase host id");
|
|
197
|
+
var endpointName = z.string().min(1).regex(/^[A-Za-z0-9_.-]+$/, "must be a hostname-like token");
|
|
198
|
+
var ipAddress = z.string().min(1).regex(/^[0-9a-fA-F:.]+$/, "must be an IP address token");
|
|
199
|
+
var sshAlias = z.string().min(1).regex(/^[A-Za-z0-9_.@-]+$/, "must be an SSH alias token");
|
|
200
|
+
var sshUser = z.string().min(1).regex(/^[A-Za-z0-9_.-]+$/, "must be an SSH user token");
|
|
201
|
+
var absolutePath = z.string().min(1).regex(/^\//, "must be an absolute path");
|
|
202
|
+
var FleetAccessProfileSchema = z.object({
|
|
203
|
+
status: z.enum(["local", "configured", "planned", "absent", "unknown"]),
|
|
204
|
+
ssh_aliases: z.array(sshAlias).optional(),
|
|
205
|
+
users: z.array(sshUser).optional(),
|
|
206
|
+
transports: z.array(z.enum(["local", "public-ip", "tailscale", "private-lan"])).min(1)
|
|
207
|
+
}).strict();
|
|
208
|
+
var FleetHostIdentitySchema = z.object({
|
|
209
|
+
hostnames: z.array(endpointName).min(1),
|
|
210
|
+
public_addresses: z.array(ipAddress).optional(),
|
|
211
|
+
private_addresses: z.array(ipAddress).optional(),
|
|
212
|
+
tailscale: z.object({
|
|
213
|
+
node_names: z.array(endpointName).optional(),
|
|
214
|
+
magicdns_names: z.array(endpointName).optional(),
|
|
215
|
+
addresses: z.array(ipAddress).optional()
|
|
216
|
+
}).strict().optional()
|
|
217
|
+
}).strict();
|
|
218
|
+
var FleetSkillwikiSatelliteSchema = z.object({
|
|
219
|
+
enabled: z.boolean(),
|
|
220
|
+
user: sshUser,
|
|
221
|
+
vault_path: absolutePath,
|
|
222
|
+
repo_path: absolutePath,
|
|
223
|
+
ssh_alias: sshAlias,
|
|
224
|
+
scheduler: z.enum(["systemd"]),
|
|
225
|
+
timezone: z.string().min(1).optional(),
|
|
226
|
+
jobs: z.array(z.enum([
|
|
227
|
+
"self-update-check",
|
|
228
|
+
"vault-sync-preflight",
|
|
229
|
+
"agent-memory-trends-daily",
|
|
230
|
+
"session-brief-refresh",
|
|
231
|
+
"health-summary"
|
|
232
|
+
])).min(1),
|
|
233
|
+
cadence: z.object({
|
|
234
|
+
self_update_check: z.literal("every-4-hours").optional(),
|
|
235
|
+
daily_window: z.string().min(1).optional()
|
|
236
|
+
}).strict().optional()
|
|
237
|
+
}).strict();
|
|
238
|
+
var FleetHostSchema = z.object({
|
|
239
|
+
class: z.enum(["dev-macos", "dev-linux", "prod-linux", "unknown"]),
|
|
240
|
+
role: z.enum(["leaf", "snapshotter"]),
|
|
241
|
+
writes_to: z.array(z.enum(["s3", "github"])).min(1),
|
|
242
|
+
protected: z.boolean().optional(),
|
|
243
|
+
identity: FleetHostIdentitySchema,
|
|
244
|
+
access: z.object({
|
|
245
|
+
from: z.record(hostId, FleetAccessProfileSchema).optional()
|
|
246
|
+
}).strict().optional(),
|
|
247
|
+
maintenance: z.object({
|
|
248
|
+
skillwiki_satellite: FleetSkillwikiSatelliteSchema.optional()
|
|
249
|
+
}).strict().optional()
|
|
250
|
+
}).strict();
|
|
251
|
+
var FleetManifestSchema = z.object({
|
|
252
|
+
"$schema": z.string().url().optional(),
|
|
253
|
+
schema_version: z.literal(1),
|
|
254
|
+
vault_remote: z.string().min(1),
|
|
255
|
+
s3_remote: z.string().min(1).optional(),
|
|
256
|
+
/** Optional host-id allowed to materialize root index.md/log.md projections. */
|
|
257
|
+
projection_authority: hostId.optional(),
|
|
258
|
+
hosts: z.record(hostId, FleetHostSchema)
|
|
259
|
+
}).strict().superRefine((v, ctx) => {
|
|
260
|
+
const entries = Object.entries(v.hosts);
|
|
261
|
+
if (entries.length === 0) {
|
|
262
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["hosts"], message: "must contain at least one host" });
|
|
263
|
+
}
|
|
264
|
+
const snapshotters = entries.filter(([, host]) => host.role === "snapshotter").map(([id]) => id);
|
|
265
|
+
if (snapshotters.length !== 1) {
|
|
266
|
+
ctx.addIssue({
|
|
267
|
+
code: z.ZodIssueCode.custom,
|
|
268
|
+
path: ["hosts"],
|
|
269
|
+
message: `must contain exactly one snapshotter host, found ${snapshotters.length}`
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (v.projection_authority) {
|
|
273
|
+
if (!v.hosts[v.projection_authority]) {
|
|
274
|
+
ctx.addIssue({
|
|
275
|
+
code: z.ZodIssueCode.custom,
|
|
276
|
+
path: ["projection_authority"],
|
|
277
|
+
message: `projection_authority host \`${v.projection_authority}\` is not in hosts`
|
|
278
|
+
});
|
|
279
|
+
} else if (snapshotters.length === 1 && v.projection_authority !== snapshotters[0]) {
|
|
280
|
+
ctx.addIssue({
|
|
281
|
+
code: z.ZodIssueCode.custom,
|
|
282
|
+
path: ["projection_authority"],
|
|
283
|
+
message: "projection_authority must be the sole snapshotter host"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
function detectSchema(fm) {
|
|
289
|
+
const COMPOUND_TYPES = /* @__PURE__ */ new Set(["lesson", "pattern", "antipattern", "gotcha"]);
|
|
290
|
+
if (typeof fm.type === "string" && COMPOUND_TYPES.has(fm.type) && "project" in fm) return { schema: "compound" };
|
|
291
|
+
if (fm.type === "meta") return { schema: "meta" };
|
|
292
|
+
if ("type" in fm && "sources" in fm) return { schema: "typed-knowledge" };
|
|
293
|
+
if ("ingested" in fm && ("source_url" in fm || "sha256" in fm)) return { schema: "raw" };
|
|
294
|
+
const RAW_KINDS = /* @__PURE__ */ new Set(["postmortem", "session-log", "meeting-notes", "other", "idea", "bug", "task", "note"]);
|
|
295
|
+
if ("ingested" in fm && typeof fm.kind === "string" && RAW_KINDS.has(fm.kind)) return { schema: "raw" };
|
|
296
|
+
if ("kind" in fm && "status" in fm) return { schema: "work-item" };
|
|
297
|
+
return { schema: null };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ../shared/src/blocked-hosts.ts
|
|
301
|
+
var METADATA_HOSTS = /* @__PURE__ */ new Set([
|
|
302
|
+
"metadata.google.internal",
|
|
303
|
+
"metadata"
|
|
304
|
+
]);
|
|
305
|
+
var METADATA_IPS = /* @__PURE__ */ new Set(["169.254.169.254"]);
|
|
306
|
+
function ipv4ToInt(ip) {
|
|
307
|
+
const parts = ip.split(".");
|
|
308
|
+
if (parts.length !== 4) return null;
|
|
309
|
+
let n = 0;
|
|
310
|
+
for (const p of parts) {
|
|
311
|
+
const v = Number(p);
|
|
312
|
+
if (!Number.isInteger(v) || v < 0 || v > 255) return null;
|
|
313
|
+
n = (n << 8) + v;
|
|
314
|
+
}
|
|
315
|
+
return n >>> 0;
|
|
316
|
+
}
|
|
317
|
+
function inRange(ip, baseStr, prefix) {
|
|
318
|
+
const ipN = ipv4ToInt(ip);
|
|
319
|
+
const baseN = ipv4ToInt(baseStr);
|
|
320
|
+
if (ipN === null || baseN === null) return false;
|
|
321
|
+
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
322
|
+
return (ipN & mask) === (baseN & mask);
|
|
323
|
+
}
|
|
324
|
+
function isBlockedHost(host) {
|
|
325
|
+
const lower = host.toLowerCase();
|
|
326
|
+
if (METADATA_HOSTS.has(lower)) return true;
|
|
327
|
+
if (METADATA_IPS.has(host)) return true;
|
|
328
|
+
if (lower === "::1") return true;
|
|
329
|
+
if (lower.startsWith("fe80:")) return true;
|
|
330
|
+
if (ipv4ToInt(host) === null) return false;
|
|
331
|
+
if (inRange(host, "10.0.0.0", 8)) return true;
|
|
332
|
+
if (inRange(host, "172.16.0.0", 12)) return true;
|
|
333
|
+
if (inRange(host, "192.168.0.0", 16)) return true;
|
|
334
|
+
if (inRange(host, "169.254.0.0", 16)) return true;
|
|
335
|
+
if (inRange(host, "127.0.0.0", 8)) return true;
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ../shared/src/error-message.ts
|
|
340
|
+
function getErrorMessage(e) {
|
|
341
|
+
return e instanceof Error ? e.message : String(e);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export {
|
|
345
|
+
ExitCode,
|
|
346
|
+
ok,
|
|
347
|
+
err,
|
|
348
|
+
TypedKnowledgeSchema,
|
|
349
|
+
RawSourceSchema,
|
|
350
|
+
WorkItemSchema,
|
|
351
|
+
CompoundSchema,
|
|
352
|
+
MetaSchema,
|
|
353
|
+
FleetManifestSchema,
|
|
354
|
+
detectSchema,
|
|
355
|
+
isBlockedHost,
|
|
356
|
+
getErrorMessage
|
|
357
|
+
};
|