skillwiki 0.9.7 → 0.9.9
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/cli.js +714 -71
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-TPS5XD2J.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
|
-
import { join as
|
|
17
|
+
import { join as join49 } from "path";
|
|
18
18
|
import { Command as Command2 } from "commander";
|
|
19
19
|
|
|
20
20
|
// ../shared/src/exit-codes.ts
|
|
@@ -2360,9 +2360,9 @@ async function runStale(input) {
|
|
|
2360
2360
|
if (input.project && !project.includes(input.project)) continue;
|
|
2361
2361
|
let inferred = false;
|
|
2362
2362
|
if (input.forceScan && !kind) {
|
|
2363
|
-
const
|
|
2364
|
-
if (!LOOP_CYCLE_PATTERN.test(
|
|
2365
|
-
const m =
|
|
2363
|
+
const basename3 = t.relPath.split("/").pop();
|
|
2364
|
+
if (!LOOP_CYCLE_PATTERN.test(basename3)) {
|
|
2365
|
+
const m = basename3.match(KIND_FROM_FILENAME);
|
|
2366
2366
|
if (m) {
|
|
2367
2367
|
kind = m[1];
|
|
2368
2368
|
inferred = true;
|
|
@@ -3499,6 +3499,7 @@ function buildCliSurface() {
|
|
|
3499
3499
|
program2.command("seed").option("--wiki <name>");
|
|
3500
3500
|
program2.command("observe").requiredOption("--text <text>").option("--kind <kind>").option("--project <slug>").option("--wiki <name>");
|
|
3501
3501
|
program2.command("session-brief").option("--project <slug>").option("--write").option("--wiki <name>");
|
|
3502
|
+
program2.command("memory");
|
|
3502
3503
|
program2.command("ingest").requiredOption("--vault <path>").requiredOption("--type <type>").requiredOption("--title <title>").option("--tags <csv>").option("--provenance <provenance>").option("--dry-run");
|
|
3503
3504
|
program2.command("fleet");
|
|
3504
3505
|
const graphCmd = program2.commands.find((c) => c.name() === "graph");
|
|
@@ -3524,6 +3525,11 @@ function buildCliSurface() {
|
|
|
3524
3525
|
const backupCmd2 = program2.commands.find((c) => c.name() === "backup");
|
|
3525
3526
|
backupCmd2.command("sync").option("--dry-run").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--prune").option("--wiki <name>");
|
|
3526
3527
|
backupCmd2.command("restore").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--target <dir>").option("--wiki <name>");
|
|
3528
|
+
const memoryCmd2 = program2.commands.find((c) => c.name() === "memory");
|
|
3529
|
+
memoryCmd2.command("topics").option("--project <slug>").option("--limit <n>").option("--wiki <name>");
|
|
3530
|
+
memoryCmd2.command("index").requiredOption("--project <slug>").option("--wiki <name>");
|
|
3531
|
+
memoryCmd2.command("recall").requiredOption("--project <slug>").requiredOption("--topic <slug>").option("--limit <n>").option("--wiki <name>");
|
|
3532
|
+
memoryCmd2.command("import").requiredOption("--from <path>").requiredOption("--project <slug>").option("--dry-run").option("--apply").option("--max-bytes <n>").option("--wiki <name>");
|
|
3527
3533
|
const fleetCmd2 = program2.commands.find((c) => c.name() === "fleet");
|
|
3528
3534
|
fleetCmd2.command("validate");
|
|
3529
3535
|
fleetCmd2.command("context").option("--file <path>").option("--host-id <id>");
|
|
@@ -6381,6 +6387,8 @@ function vaultSyncChecks(input) {
|
|
|
6381
6387
|
const requiredExcludes = [
|
|
6382
6388
|
"remotely-save/data.json",
|
|
6383
6389
|
".skillwiki/sync.lock",
|
|
6390
|
+
".skillwiki/memory/",
|
|
6391
|
+
".skillwiki/memory-topics.json",
|
|
6384
6392
|
".claude/settings.local.json"
|
|
6385
6393
|
];
|
|
6386
6394
|
const missing = requiredExcludes.filter((ex) => !content.includes(ex));
|
|
@@ -6762,7 +6770,13 @@ function runVaultSyncHealth(home, syncMode) {
|
|
|
6762
6770
|
checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
|
|
6763
6771
|
} else {
|
|
6764
6772
|
const content = readFileSync8(filterPath, "utf8");
|
|
6765
|
-
const missing = [
|
|
6773
|
+
const missing = [
|
|
6774
|
+
"remotely-save/data.json",
|
|
6775
|
+
".skillwiki/sync.lock",
|
|
6776
|
+
".skillwiki/memory/",
|
|
6777
|
+
".skillwiki/memory-topics.json",
|
|
6778
|
+
".claude/settings.local.json"
|
|
6779
|
+
].filter((item) => !content.includes(item));
|
|
6766
6780
|
checks.push(missing.length > 0 ? { id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "warn", detail: `Missing excludes: ${missing.join(", ")}` } : { id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "pass", detail: "Required excludes present" });
|
|
6767
6781
|
}
|
|
6768
6782
|
checks.push({ id: "vault_sync_snapshot_guard", label: "Snapshot script guard", status: "pass", detail: "Not a snapshotter host \u2014 check skipped" });
|
|
@@ -8329,6 +8343,7 @@ async function runSessionBrief(input) {
|
|
|
8329
8343
|
const workItems = await loadWorkItems(scan.data.workItems);
|
|
8330
8344
|
const digests = await loadTrendDigests(scan.data.typedKnowledge);
|
|
8331
8345
|
const healthWarnings = await loadHealthWarnings(input.vault);
|
|
8346
|
+
const memoryTopics = project ? await loadMemoryTopics(input.vault, project) : [];
|
|
8332
8347
|
const latestLogs = newest(transcripts.filter((t) => t.kind === "session-log"), 3);
|
|
8333
8348
|
const unclaimedCaptures = newest(transcripts.filter((t) => {
|
|
8334
8349
|
if (t.kind !== "task" && t.kind !== "bug") return false;
|
|
@@ -8348,6 +8363,7 @@ async function runSessionBrief(input) {
|
|
|
8348
8363
|
activeWork,
|
|
8349
8364
|
latestDigest,
|
|
8350
8365
|
projectLogs,
|
|
8366
|
+
memoryTopics,
|
|
8351
8367
|
healthWarnings
|
|
8352
8368
|
}), MAX_WORDS);
|
|
8353
8369
|
let filesWritten = [];
|
|
@@ -8359,7 +8375,8 @@ async function runSessionBrief(input) {
|
|
|
8359
8375
|
brief,
|
|
8360
8376
|
generatedAt,
|
|
8361
8377
|
today,
|
|
8362
|
-
wordCount: countWords(brief)
|
|
8378
|
+
wordCount: countWords(brief),
|
|
8379
|
+
memoryTopics
|
|
8363
8380
|
});
|
|
8364
8381
|
filesWritten = writeResult.filesWritten;
|
|
8365
8382
|
indexUpdated = writeResult.indexUpdated;
|
|
@@ -8376,6 +8393,7 @@ async function runSessionBrief(input) {
|
|
|
8376
8393
|
index_updated: indexUpdated,
|
|
8377
8394
|
log_updated: logUpdated,
|
|
8378
8395
|
generated_at: generatedAt,
|
|
8396
|
+
memory_topics: memoryTopics,
|
|
8379
8397
|
humanHint
|
|
8380
8398
|
})
|
|
8381
8399
|
};
|
|
@@ -8488,6 +8506,7 @@ function renderBrief(input) {
|
|
|
8488
8506
|
appendSection(lines, "Unclaimed Captures", input.unclaimedCaptures, "No unclaimed task or bug captures found.");
|
|
8489
8507
|
appendSection(lines, "Recent Session Logs", input.project ? input.projectLogs : input.latestLogs, "No recent session logs found.");
|
|
8490
8508
|
appendSection(lines, "Latest Agent Memory Trends", input.latestDigest, "No agent memory trends digest found.");
|
|
8509
|
+
appendMemoryTopicsSection(lines, input.project, input.memoryTopics);
|
|
8491
8510
|
appendTextSection(lines, "Health Warnings", input.healthWarnings, "No high-level health warnings found.");
|
|
8492
8511
|
lines.push(
|
|
8493
8512
|
"## Suggested Commands",
|
|
@@ -8499,6 +8518,15 @@ function renderBrief(input) {
|
|
|
8499
8518
|
);
|
|
8500
8519
|
return lines.join("\n").trimEnd() + "\n";
|
|
8501
8520
|
}
|
|
8521
|
+
function appendMemoryTopicsSection(lines, project, topics) {
|
|
8522
|
+
if (!project || topics.length === 0) return;
|
|
8523
|
+
lines.push("## Memory Topics", "");
|
|
8524
|
+
for (const topic of topics.slice(0, 5)) {
|
|
8525
|
+
const sourceCount = topic.paths.length === 1 ? "1 source" : `${topic.paths.length} sources`;
|
|
8526
|
+
lines.push(`- ${topic.updated} ${topic.name} (${sourceCount}) \u2014 ${topic.summary}; recall: \`skillwiki memory recall --project ${project} --topic ${topic.name}\``);
|
|
8527
|
+
}
|
|
8528
|
+
lines.push("");
|
|
8529
|
+
}
|
|
8502
8530
|
function appendSection(lines, title, items, empty) {
|
|
8503
8531
|
lines.push(`## ${title}`, "");
|
|
8504
8532
|
if (items.length === 0) {
|
|
@@ -8537,6 +8565,23 @@ async function loadHealthWarnings(vault) {
|
|
|
8537
8565
|
return [];
|
|
8538
8566
|
}
|
|
8539
8567
|
}
|
|
8568
|
+
async function loadMemoryTopics(vault, project) {
|
|
8569
|
+
const text = await readIfExists2(join38(vault, ".skillwiki", "memory", project, "topics.json"));
|
|
8570
|
+
if (!text) return [];
|
|
8571
|
+
try {
|
|
8572
|
+
const parsed = JSON.parse(text);
|
|
8573
|
+
if (!Array.isArray(parsed.topics)) return [];
|
|
8574
|
+
return parsed.topics.filter((topic) => typeof topic === "object" && topic !== null && !Array.isArray(topic)).map((topic) => ({
|
|
8575
|
+
name: stringField(topic.name),
|
|
8576
|
+
summary: stringField(topic.summary),
|
|
8577
|
+
project: stringField(topic.project) || void 0,
|
|
8578
|
+
updated: stringField(topic.updated),
|
|
8579
|
+
paths: Array.isArray(topic.paths) ? topic.paths.filter((path) => typeof path === "string") : []
|
|
8580
|
+
})).filter((topic) => topic.name && topic.summary && topic.updated && topic.paths.length > 0).sort((a, b) => b.updated.localeCompare(a.updated) || a.name.localeCompare(b.name)).slice(0, 5);
|
|
8581
|
+
} catch {
|
|
8582
|
+
return [];
|
|
8583
|
+
}
|
|
8584
|
+
}
|
|
8540
8585
|
async function writeBriefArtifacts(vault, input) {
|
|
8541
8586
|
const metaPath = join38(vault, "meta", "latest-session-brief.md");
|
|
8542
8587
|
const cacheMdPath = join38(vault, ".skillwiki", "session-brief.md");
|
|
@@ -8553,7 +8598,8 @@ async function writeBriefArtifacts(vault, input) {
|
|
|
8553
8598
|
project: input.project,
|
|
8554
8599
|
brief: input.brief,
|
|
8555
8600
|
word_count: input.wordCount,
|
|
8556
|
-
generated_at: input.generatedAt
|
|
8601
|
+
generated_at: input.generatedAt,
|
|
8602
|
+
memory_topics: input.memoryTopics
|
|
8557
8603
|
}, null, 2)}
|
|
8558
8604
|
`, "utf8");
|
|
8559
8605
|
const indexUpdated = await ensureIndexEntry(vault);
|
|
@@ -8589,7 +8635,7 @@ function renderCommittedBrief(input) {
|
|
|
8589
8635
|
`created: ${input.today}`,
|
|
8590
8636
|
`updated: ${input.today}`,
|
|
8591
8637
|
"type: meta",
|
|
8592
|
-
"tags: [
|
|
8638
|
+
"tags: [meta, session-brief]",
|
|
8593
8639
|
"confidence: high",
|
|
8594
8640
|
"generated_by: skillwiki session-brief",
|
|
8595
8641
|
`generated_at: ${input.generatedAt}`,
|
|
@@ -8672,10 +8718,553 @@ function dateFromPath(path) {
|
|
|
8672
8718
|
return path.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? "";
|
|
8673
8719
|
}
|
|
8674
8720
|
|
|
8675
|
-
// src/commands/
|
|
8676
|
-
import { readFile as readFile26, writeFile as writeFile15, mkdir as mkdir14 } from "fs/promises";
|
|
8677
|
-
import { join as join39 } from "path";
|
|
8721
|
+
// src/commands/memory.ts
|
|
8678
8722
|
import { createHash as createHash8 } from "crypto";
|
|
8723
|
+
import { mkdir as mkdir14, readFile as readFile26, readdir as readdir8, stat as stat9, writeFile as writeFile15 } from "fs/promises";
|
|
8724
|
+
import { basename as basename2, extname, join as join39, relative as relative4, sep as sep4 } from "path";
|
|
8725
|
+
async function runMemoryTopics(input) {
|
|
8726
|
+
const scan = await scanVault(input.vault);
|
|
8727
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
8728
|
+
const cacheText = await readMemoryCache(input.vault, input.project);
|
|
8729
|
+
if (!cacheText) {
|
|
8730
|
+
return {
|
|
8731
|
+
exitCode: ExitCode.OK,
|
|
8732
|
+
result: ok({
|
|
8733
|
+
cache_present: false,
|
|
8734
|
+
topics: [],
|
|
8735
|
+
files_written: [],
|
|
8736
|
+
humanHint: "no memory topics cache found"
|
|
8737
|
+
})
|
|
8738
|
+
};
|
|
8739
|
+
}
|
|
8740
|
+
let parsed;
|
|
8741
|
+
try {
|
|
8742
|
+
parsed = JSON.parse(cacheText);
|
|
8743
|
+
} catch (e) {
|
|
8744
|
+
return {
|
|
8745
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
8746
|
+
result: err("WRITE_FAILED", {
|
|
8747
|
+
path: ".skillwiki/memory-topics.json",
|
|
8748
|
+
message: `invalid memory topics cache: ${String(e)}`
|
|
8749
|
+
})
|
|
8750
|
+
};
|
|
8751
|
+
}
|
|
8752
|
+
const limit = normalizeLimit(input.limit);
|
|
8753
|
+
const topics = normalizeTopics(parsed.topics).filter((topic) => !input.project || topic.project === input.project).sort(compareTopics).slice(0, limit);
|
|
8754
|
+
return {
|
|
8755
|
+
exitCode: ExitCode.OK,
|
|
8756
|
+
result: ok({
|
|
8757
|
+
generated_at: stringField2(parsed.generated_at) || void 0,
|
|
8758
|
+
cache_present: true,
|
|
8759
|
+
topics,
|
|
8760
|
+
files_written: [],
|
|
8761
|
+
humanHint: renderHumanHint(topics)
|
|
8762
|
+
})
|
|
8763
|
+
};
|
|
8764
|
+
}
|
|
8765
|
+
async function runMemoryIndex(input) {
|
|
8766
|
+
const scan = await scanVault(input.vault);
|
|
8767
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
8768
|
+
const warnings = [];
|
|
8769
|
+
const sourcePages = dedupePages(scan.data.allMarkdown);
|
|
8770
|
+
const sources = [];
|
|
8771
|
+
for (const page of sourcePages) {
|
|
8772
|
+
const source = await readMemorySource(page, input.project, warnings);
|
|
8773
|
+
if (source) sources.push(source);
|
|
8774
|
+
}
|
|
8775
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8776
|
+
const topics = buildTopics(input.project, sources);
|
|
8777
|
+
const relCachePath = memoryCacheRelPath(input.project);
|
|
8778
|
+
const absCachePath = join39(input.vault, relCachePath);
|
|
8779
|
+
await mkdir14(join39(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
|
|
8780
|
+
await writeFile15(absCachePath, `${JSON.stringify({
|
|
8781
|
+
generated_at: generatedAt,
|
|
8782
|
+
project: input.project,
|
|
8783
|
+
topics,
|
|
8784
|
+
sources,
|
|
8785
|
+
warnings
|
|
8786
|
+
}, null, 2)}
|
|
8787
|
+
`, "utf8");
|
|
8788
|
+
return {
|
|
8789
|
+
exitCode: ExitCode.OK,
|
|
8790
|
+
result: ok({
|
|
8791
|
+
project: input.project,
|
|
8792
|
+
generated_at: generatedAt,
|
|
8793
|
+
topic_count: topics.length,
|
|
8794
|
+
source_count: sources.length,
|
|
8795
|
+
topics,
|
|
8796
|
+
files_written: [relCachePath],
|
|
8797
|
+
warnings,
|
|
8798
|
+
humanHint: `indexed ${sources.length} memory sources into ${topics.length} topics for ${input.project}`
|
|
8799
|
+
})
|
|
8800
|
+
};
|
|
8801
|
+
}
|
|
8802
|
+
async function runMemoryRecall(input) {
|
|
8803
|
+
const scan = await scanVault(input.vault);
|
|
8804
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
8805
|
+
const cacheText = await readMemoryCache(input.vault, input.project);
|
|
8806
|
+
if (!cacheText) {
|
|
8807
|
+
return {
|
|
8808
|
+
exitCode: ExitCode.OK,
|
|
8809
|
+
result: ok({
|
|
8810
|
+
project: input.project,
|
|
8811
|
+
topic: input.topic,
|
|
8812
|
+
sources: [],
|
|
8813
|
+
humanHint: `no memory topics cache found for project ${input.project}`
|
|
8814
|
+
})
|
|
8815
|
+
};
|
|
8816
|
+
}
|
|
8817
|
+
let parsed;
|
|
8818
|
+
try {
|
|
8819
|
+
parsed = JSON.parse(cacheText);
|
|
8820
|
+
} catch (e) {
|
|
8821
|
+
return {
|
|
8822
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
8823
|
+
result: err("WRITE_FAILED", {
|
|
8824
|
+
path: memoryCacheRelPath(input.project),
|
|
8825
|
+
message: `invalid memory topics cache: ${String(e)}`
|
|
8826
|
+
})
|
|
8827
|
+
};
|
|
8828
|
+
}
|
|
8829
|
+
const limit = normalizeLimit(input.limit);
|
|
8830
|
+
const sources = normalizeSources(parsed.sources).filter((source) => source.topics.includes(input.topic)).filter((source) => source.memory_privacy !== "secret-blocked").sort(compareSources).slice(0, limit);
|
|
8831
|
+
return {
|
|
8832
|
+
exitCode: ExitCode.OK,
|
|
8833
|
+
result: ok({
|
|
8834
|
+
project: input.project,
|
|
8835
|
+
topic: input.topic,
|
|
8836
|
+
sources,
|
|
8837
|
+
humanHint: renderRecallHint(input.topic, sources)
|
|
8838
|
+
})
|
|
8839
|
+
};
|
|
8840
|
+
}
|
|
8841
|
+
async function runMemoryImport(input) {
|
|
8842
|
+
const scan = await scanVault(input.vault);
|
|
8843
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
8844
|
+
const maxBytes = input.maxBytes ?? 2e5;
|
|
8845
|
+
let files;
|
|
8846
|
+
try {
|
|
8847
|
+
files = await collectImportFiles(input.from);
|
|
8848
|
+
} catch (e) {
|
|
8849
|
+
return {
|
|
8850
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
8851
|
+
result: err("WRITE_FAILED", { path: input.from, message: String(e) })
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8854
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8855
|
+
const today = generatedAt.slice(0, 10);
|
|
8856
|
+
const entries = [];
|
|
8857
|
+
const filesWritten = [];
|
|
8858
|
+
for (const file of files) {
|
|
8859
|
+
let entry;
|
|
8860
|
+
try {
|
|
8861
|
+
entry = await buildImportEntry(file, input.from, input.project, today, maxBytes);
|
|
8862
|
+
if (input.apply && entry.status === "ready" && entry.proposed_path) {
|
|
8863
|
+
const written2 = await writeImportCapture(input.vault, entry, today);
|
|
8864
|
+
entry.status = "written";
|
|
8865
|
+
entry.written_path = written2.relPath;
|
|
8866
|
+
entry.validation = written2.validation;
|
|
8867
|
+
filesWritten.push(written2.relPath);
|
|
8868
|
+
}
|
|
8869
|
+
} catch (e) {
|
|
8870
|
+
return {
|
|
8871
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
8872
|
+
result: err("WRITE_FAILED", { path: file, message: String(e) })
|
|
8873
|
+
};
|
|
8874
|
+
}
|
|
8875
|
+
entries.push(entry);
|
|
8876
|
+
}
|
|
8877
|
+
const manifest = {
|
|
8878
|
+
project: input.project,
|
|
8879
|
+
generated_at: generatedAt,
|
|
8880
|
+
source_root: input.from,
|
|
8881
|
+
entries
|
|
8882
|
+
};
|
|
8883
|
+
const ready = entries.filter((entry) => entry.status === "ready").length;
|
|
8884
|
+
const written = entries.filter((entry) => entry.status === "written").length;
|
|
8885
|
+
const rejected = entries.filter((entry) => entry.status === "rejected").length;
|
|
8886
|
+
return {
|
|
8887
|
+
exitCode: ExitCode.OK,
|
|
8888
|
+
result: ok({
|
|
8889
|
+
applied: !!input.apply,
|
|
8890
|
+
manifest,
|
|
8891
|
+
files_written: filesWritten,
|
|
8892
|
+
humanHint: input.apply ? `memory import applied: ${written} written, ${rejected} rejected` : `memory import dry-run: ${ready} ready, ${rejected} rejected`
|
|
8893
|
+
})
|
|
8894
|
+
};
|
|
8895
|
+
}
|
|
8896
|
+
async function readIfExists3(path) {
|
|
8897
|
+
try {
|
|
8898
|
+
return await readFile26(path, "utf8");
|
|
8899
|
+
} catch {
|
|
8900
|
+
return "";
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
async function collectImportFiles(source) {
|
|
8904
|
+
const st = await stat9(source);
|
|
8905
|
+
if (st.isFile()) return isImportCandidate(source) ? [source] : [];
|
|
8906
|
+
const files = [];
|
|
8907
|
+
await walkImportFiles(source, files);
|
|
8908
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
8909
|
+
}
|
|
8910
|
+
async function walkImportFiles(dir, out) {
|
|
8911
|
+
const entries = await readdir8(dir, { withFileTypes: true });
|
|
8912
|
+
for (const entry of entries) {
|
|
8913
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
8914
|
+
const path = join39(dir, entry.name);
|
|
8915
|
+
if (entry.isDirectory()) {
|
|
8916
|
+
await walkImportFiles(path, out);
|
|
8917
|
+
} else if (entry.isFile() && isImportCandidate(path)) {
|
|
8918
|
+
out.push(path);
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
}
|
|
8922
|
+
function isImportCandidate(path) {
|
|
8923
|
+
const ext = extname(path).toLowerCase();
|
|
8924
|
+
return ext === ".md" || ext === ".txt";
|
|
8925
|
+
}
|
|
8926
|
+
async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
|
|
8927
|
+
const st = await stat9(file);
|
|
8928
|
+
const sourceKind = classifyImportSource(file);
|
|
8929
|
+
const hash = createHash8("sha256").update(await readFile26(file)).digest("hex");
|
|
8930
|
+
const baseEntry = {
|
|
8931
|
+
source_path: file,
|
|
8932
|
+
source_kind: sourceKind,
|
|
8933
|
+
sha256: hash,
|
|
8934
|
+
memory_kind: defaultImportKind(sourceKind),
|
|
8935
|
+
memory_privacy: "local",
|
|
8936
|
+
redaction_count: 0
|
|
8937
|
+
};
|
|
8938
|
+
if (st.size > maxBytes) {
|
|
8939
|
+
return {
|
|
8940
|
+
...baseEntry,
|
|
8941
|
+
status: "rejected",
|
|
8942
|
+
reason: "oversized_source"
|
|
8943
|
+
};
|
|
8944
|
+
}
|
|
8945
|
+
if (sourceKind === "codex-rule" || sourceKind === "agents-policy") {
|
|
8946
|
+
return {
|
|
8947
|
+
...baseEntry,
|
|
8948
|
+
status: "rejected",
|
|
8949
|
+
reason: "policy_source_not_imported"
|
|
8950
|
+
};
|
|
8951
|
+
}
|
|
8952
|
+
const text = await readFile26(file, "utf8");
|
|
8953
|
+
const extracted = extractImportText(text, sourceKind);
|
|
8954
|
+
if (!extracted) {
|
|
8955
|
+
return {
|
|
8956
|
+
...baseEntry,
|
|
8957
|
+
status: "rejected",
|
|
8958
|
+
reason: "policy_source_not_imported"
|
|
8959
|
+
};
|
|
8960
|
+
}
|
|
8961
|
+
const redacted = redactSensitiveContent(extracted, { file });
|
|
8962
|
+
const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
|
|
8963
|
+
const sourceSlug = slugify3(basename2(file, extname(file)));
|
|
8964
|
+
const relSource = relative4(sourceRoot, file).split(sep4).join("/");
|
|
8965
|
+
const entry = {
|
|
8966
|
+
...baseEntry,
|
|
8967
|
+
status: "ready",
|
|
8968
|
+
memory_privacy: privacy,
|
|
8969
|
+
redaction_count: redacted.findings.length,
|
|
8970
|
+
proposed_path: `raw/transcripts/${today}-memory-import-${sourceSlug}.md`,
|
|
8971
|
+
source_path: relSource || file
|
|
8972
|
+
};
|
|
8973
|
+
Object.defineProperty(entry, "__content", { value: redacted.text, enumerable: false });
|
|
8974
|
+
Object.defineProperty(entry, "__project", { value: project, enumerable: false });
|
|
8975
|
+
return entry;
|
|
8976
|
+
}
|
|
8977
|
+
async function writeImportCapture(vault, entry, today) {
|
|
8978
|
+
const content = hiddenString(entry, "__content");
|
|
8979
|
+
const project = hiddenString(entry, "__project");
|
|
8980
|
+
const relPath = await availableImportPath(vault, entry.proposed_path);
|
|
8981
|
+
const absPath = join39(vault, relPath);
|
|
8982
|
+
await mkdir14(join39(vault, "raw", "transcripts"), { recursive: true });
|
|
8983
|
+
await writeFile15(absPath, renderImportCapture(entry, content, project, today), "utf8");
|
|
8984
|
+
const validation = await runValidate({ file: absPath });
|
|
8985
|
+
return {
|
|
8986
|
+
relPath,
|
|
8987
|
+
validation: {
|
|
8988
|
+
valid: validation.exitCode === ExitCode.OK,
|
|
8989
|
+
exit_code: validation.exitCode
|
|
8990
|
+
}
|
|
8991
|
+
};
|
|
8992
|
+
}
|
|
8993
|
+
async function availableImportPath(vault, proposed) {
|
|
8994
|
+
const ext = extname(proposed);
|
|
8995
|
+
const stem = proposed.slice(0, -ext.length);
|
|
8996
|
+
let candidate = proposed;
|
|
8997
|
+
let i = 2;
|
|
8998
|
+
while (await readIfExists3(join39(vault, candidate))) {
|
|
8999
|
+
candidate = `${stem}-${i}${ext}`;
|
|
9000
|
+
i++;
|
|
9001
|
+
}
|
|
9002
|
+
return candidate;
|
|
9003
|
+
}
|
|
9004
|
+
function renderImportCapture(entry, content, project, today) {
|
|
9005
|
+
return [
|
|
9006
|
+
"---",
|
|
9007
|
+
"source_url:",
|
|
9008
|
+
`ingested: ${today}`,
|
|
9009
|
+
"kind: note",
|
|
9010
|
+
`project: "[[${project}]]"`,
|
|
9011
|
+
`memory_kind: ${entry.memory_kind}`,
|
|
9012
|
+
"memory_topics: [imported-memory]",
|
|
9013
|
+
"memory_scope: project",
|
|
9014
|
+
"memory_policy: historical",
|
|
9015
|
+
`memory_privacy: ${entry.memory_privacy}`,
|
|
9016
|
+
"memory_status: active",
|
|
9017
|
+
`source_agent: skillwiki memory import`,
|
|
9018
|
+
`source_hash: ${entry.sha256}`,
|
|
9019
|
+
`source_paths: ["${entry.source_path.replaceAll('"', '\\"')}"]`,
|
|
9020
|
+
"---",
|
|
9021
|
+
"",
|
|
9022
|
+
`# Imported Memory: ${basename2(entry.source_path, extname(entry.source_path))}`,
|
|
9023
|
+
"",
|
|
9024
|
+
`Source kind: ${entry.source_kind}`,
|
|
9025
|
+
"",
|
|
9026
|
+
"Promotion guidance: review this raw capture before promoting it to compound or typed knowledge.",
|
|
9027
|
+
"",
|
|
9028
|
+
content.trimEnd(),
|
|
9029
|
+
""
|
|
9030
|
+
].join("\n");
|
|
9031
|
+
}
|
|
9032
|
+
function hiddenString(entry, key) {
|
|
9033
|
+
return entry[key] ?? "";
|
|
9034
|
+
}
|
|
9035
|
+
function classifyImportSource(file) {
|
|
9036
|
+
const rel = file.split(sep4).join("/");
|
|
9037
|
+
const name = basename2(file);
|
|
9038
|
+
if (rel.includes("/.codex/memories/")) return "codex-memory";
|
|
9039
|
+
if (rel.includes("/.codex/rules/")) return "codex-rule";
|
|
9040
|
+
if (rel.includes("/.claude/") && name === "napkin.md") return "napkin";
|
|
9041
|
+
if (rel.includes("/.memsearch/memory/")) return "memsearch";
|
|
9042
|
+
if (name === "AGENTS.md") return "agents-policy";
|
|
9043
|
+
if (name === "CLAUDE.md") return "claude-auto-memory";
|
|
9044
|
+
return "generic";
|
|
9045
|
+
}
|
|
9046
|
+
function defaultImportKind(sourceKind) {
|
|
9047
|
+
switch (sourceKind) {
|
|
9048
|
+
case "codex-memory":
|
|
9049
|
+
case "claude-auto-memory":
|
|
9050
|
+
return "preference";
|
|
9051
|
+
case "napkin":
|
|
9052
|
+
return "correction";
|
|
9053
|
+
case "memsearch":
|
|
9054
|
+
return "handoff";
|
|
9055
|
+
default:
|
|
9056
|
+
return "handoff";
|
|
9057
|
+
}
|
|
9058
|
+
}
|
|
9059
|
+
function extractImportText(text, sourceKind) {
|
|
9060
|
+
if (sourceKind !== "claude-auto-memory") return text;
|
|
9061
|
+
const match = text.match(/<!--\s*AUTO-MEMORY:START\s*-->([\s\S]*?)<!--\s*AUTO-MEMORY:END\s*-->/i);
|
|
9062
|
+
return match?.[1]?.trim() ?? "";
|
|
9063
|
+
}
|
|
9064
|
+
function normalizeTopics(value) {
|
|
9065
|
+
if (!Array.isArray(value)) return [];
|
|
9066
|
+
const topics = [];
|
|
9067
|
+
for (const item of value) {
|
|
9068
|
+
if (!isRecord(item)) continue;
|
|
9069
|
+
const name = stringField2(item.name);
|
|
9070
|
+
const summary = stringField2(item.summary);
|
|
9071
|
+
const updated = stringField2(item.updated);
|
|
9072
|
+
const paths = stringArray(item.paths);
|
|
9073
|
+
if (!name || !summary || !updated || paths.length === 0) continue;
|
|
9074
|
+
const project = stringField2(item.project);
|
|
9075
|
+
topics.push({
|
|
9076
|
+
name,
|
|
9077
|
+
summary,
|
|
9078
|
+
...project ? { project } : {},
|
|
9079
|
+
updated,
|
|
9080
|
+
paths
|
|
9081
|
+
});
|
|
9082
|
+
}
|
|
9083
|
+
return topics;
|
|
9084
|
+
}
|
|
9085
|
+
function normalizeSources(value) {
|
|
9086
|
+
if (!Array.isArray(value)) return [];
|
|
9087
|
+
const sources = [];
|
|
9088
|
+
for (const item of value) {
|
|
9089
|
+
if (!isRecord(item)) continue;
|
|
9090
|
+
const path = stringField2(item.path);
|
|
9091
|
+
const title = stringField2(item.title);
|
|
9092
|
+
const summary = stringField2(item.summary);
|
|
9093
|
+
const updated = stringField2(item.updated);
|
|
9094
|
+
const hash = stringField2(item.hash);
|
|
9095
|
+
const topics = stringArray(item.topics);
|
|
9096
|
+
if (!path || !title || !summary || !updated || !hash || topics.length === 0) continue;
|
|
9097
|
+
const project = stringField2(item.project);
|
|
9098
|
+
sources.push({
|
|
9099
|
+
path,
|
|
9100
|
+
title,
|
|
9101
|
+
summary,
|
|
9102
|
+
updated,
|
|
9103
|
+
hash,
|
|
9104
|
+
topics,
|
|
9105
|
+
...project ? { project } : {},
|
|
9106
|
+
...stringField2(item.memory_kind) ? { memory_kind: stringField2(item.memory_kind) } : {},
|
|
9107
|
+
...stringField2(item.memory_scope) ? { memory_scope: stringField2(item.memory_scope) } : {},
|
|
9108
|
+
...stringField2(item.memory_policy) ? { memory_policy: stringField2(item.memory_policy) } : {},
|
|
9109
|
+
memory_privacy: stringField2(item.memory_privacy) || "local",
|
|
9110
|
+
memory_status: stringField2(item.memory_status) || "active"
|
|
9111
|
+
});
|
|
9112
|
+
}
|
|
9113
|
+
return sources;
|
|
9114
|
+
}
|
|
9115
|
+
function normalizeLimit(value) {
|
|
9116
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) return 10;
|
|
9117
|
+
return Math.floor(value);
|
|
9118
|
+
}
|
|
9119
|
+
function compareTopics(a, b) {
|
|
9120
|
+
return b.updated.localeCompare(a.updated) || a.name.localeCompare(b.name);
|
|
9121
|
+
}
|
|
9122
|
+
function compareSources(a, b) {
|
|
9123
|
+
return b.updated.localeCompare(a.updated) || a.path.localeCompare(b.path);
|
|
9124
|
+
}
|
|
9125
|
+
function renderHumanHint(topics) {
|
|
9126
|
+
if (topics.length === 0) return "no memory topics found";
|
|
9127
|
+
return topics.map((topic) => {
|
|
9128
|
+
const project = topic.project ? ` [${topic.project}]` : "";
|
|
9129
|
+
return `${topic.updated} ${topic.name}${project} \u2014 ${topic.summary} (${topic.paths.join(", ")})`;
|
|
9130
|
+
}).join("\n");
|
|
9131
|
+
}
|
|
9132
|
+
function renderRecallHint(topic, sources) {
|
|
9133
|
+
if (sources.length === 0) return `no memory sources found for topic ${topic}`;
|
|
9134
|
+
return sources.map((source) => `${source.updated} ${source.title} (${source.path}) \u2014 ${source.summary}`).join("\n");
|
|
9135
|
+
}
|
|
9136
|
+
function memoryCacheRelPath(project) {
|
|
9137
|
+
return `.skillwiki/memory/${project}/topics.json`;
|
|
9138
|
+
}
|
|
9139
|
+
async function readMemoryCache(vault, project) {
|
|
9140
|
+
if (project) {
|
|
9141
|
+
const projectCache = await readIfExists3(join39(vault, memoryCacheRelPath(project)));
|
|
9142
|
+
if (projectCache) return projectCache;
|
|
9143
|
+
}
|
|
9144
|
+
return readIfExists3(join39(vault, ".skillwiki", "memory-topics.json"));
|
|
9145
|
+
}
|
|
9146
|
+
function dedupePages(pages) {
|
|
9147
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9148
|
+
const out = [];
|
|
9149
|
+
for (const page of pages) {
|
|
9150
|
+
if (seen.has(page.relPath)) continue;
|
|
9151
|
+
seen.add(page.relPath);
|
|
9152
|
+
out.push(page);
|
|
9153
|
+
}
|
|
9154
|
+
return out;
|
|
9155
|
+
}
|
|
9156
|
+
async function readMemorySource(page, project, warnings) {
|
|
9157
|
+
const text = await readPage(page);
|
|
9158
|
+
const fm = extractFrontmatter(text);
|
|
9159
|
+
if (!fm.ok) return null;
|
|
9160
|
+
const topics = normalizeMemoryTopics(fm.data.memory_topics, page.relPath, warnings);
|
|
9161
|
+
if (topics.length === 0) return null;
|
|
9162
|
+
const projects = memoryProjects(page.relPath, fm.data);
|
|
9163
|
+
const scope = stringField2(fm.data.memory_scope);
|
|
9164
|
+
if (!projects.includes(project) && scope !== "global" && scope !== "cross-agent") return null;
|
|
9165
|
+
const privacy = stringField2(fm.data.memory_privacy) || "local";
|
|
9166
|
+
if (privacy === "secret-blocked") {
|
|
9167
|
+
warnings.push(`${page.relPath}: skipped secret-blocked memory`);
|
|
9168
|
+
return null;
|
|
9169
|
+
}
|
|
9170
|
+
const status = stringField2(fm.data.memory_status) || "active";
|
|
9171
|
+
if (status === "archived" || status === "rejected") return null;
|
|
9172
|
+
const split = splitFrontmatter(text);
|
|
9173
|
+
const body = split.ok ? split.data.body : text;
|
|
9174
|
+
const updated = stringField2(fm.data.last_seen) || stringField2(fm.data.updated) || stringField2(fm.data.ingested) || dateFromPath2(page.relPath);
|
|
9175
|
+
const title = stringField2(fm.data.title) || page.relPath.split("/").pop()?.replace(/\.md$/, "") || page.relPath;
|
|
9176
|
+
return {
|
|
9177
|
+
path: page.relPath,
|
|
9178
|
+
title,
|
|
9179
|
+
summary: summarize2(body),
|
|
9180
|
+
updated,
|
|
9181
|
+
hash: createHash8("sha256").update(Buffer.from(body, "utf8")).digest("hex"),
|
|
9182
|
+
topics,
|
|
9183
|
+
project,
|
|
9184
|
+
...stringField2(fm.data.memory_kind) ? { memory_kind: stringField2(fm.data.memory_kind) } : {},
|
|
9185
|
+
...scope ? { memory_scope: scope } : {},
|
|
9186
|
+
...stringField2(fm.data.memory_policy) ? { memory_policy: stringField2(fm.data.memory_policy) } : {},
|
|
9187
|
+
memory_privacy: privacy,
|
|
9188
|
+
memory_status: status
|
|
9189
|
+
};
|
|
9190
|
+
}
|
|
9191
|
+
function normalizeMemoryTopics(value, path, warnings) {
|
|
9192
|
+
const rawTopics = typeof value === "string" ? [value] : stringArray(value);
|
|
9193
|
+
const topics = [];
|
|
9194
|
+
for (const topic of rawTopics) {
|
|
9195
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(topic)) {
|
|
9196
|
+
warnings.push(`${path}: invalid memory topic slug ${topic}`);
|
|
9197
|
+
continue;
|
|
9198
|
+
}
|
|
9199
|
+
if (!topics.includes(topic)) topics.push(topic);
|
|
9200
|
+
}
|
|
9201
|
+
return topics;
|
|
9202
|
+
}
|
|
9203
|
+
function memoryProjects(path, fm) {
|
|
9204
|
+
const projects = /* @__PURE__ */ new Set();
|
|
9205
|
+
const project = wikilinkSlug2(fm.project);
|
|
9206
|
+
if (project) projects.add(project);
|
|
9207
|
+
if (Array.isArray(fm.provenance_projects)) {
|
|
9208
|
+
for (const value of fm.provenance_projects) {
|
|
9209
|
+
const slug = wikilinkSlug2(value);
|
|
9210
|
+
if (slug) projects.add(slug);
|
|
9211
|
+
}
|
|
9212
|
+
}
|
|
9213
|
+
const pathProject = path.match(/^projects\/([^/]+)\//)?.[1];
|
|
9214
|
+
if (pathProject) projects.add(pathProject);
|
|
9215
|
+
return [...projects];
|
|
9216
|
+
}
|
|
9217
|
+
function buildTopics(project, sources) {
|
|
9218
|
+
const byTopic = /* @__PURE__ */ new Map();
|
|
9219
|
+
for (const source of sources) {
|
|
9220
|
+
for (const topic of source.topics) {
|
|
9221
|
+
const list = byTopic.get(topic) ?? [];
|
|
9222
|
+
list.push(source);
|
|
9223
|
+
byTopic.set(topic, list);
|
|
9224
|
+
}
|
|
9225
|
+
}
|
|
9226
|
+
return [...byTopic.entries()].map(([name, topicSources]) => {
|
|
9227
|
+
const sorted = [...topicSources].sort(compareSources);
|
|
9228
|
+
return {
|
|
9229
|
+
name,
|
|
9230
|
+
project,
|
|
9231
|
+
summary: sorted[0]?.summary ?? "",
|
|
9232
|
+
updated: sorted[0]?.updated ?? "",
|
|
9233
|
+
paths: sorted.map((source) => source.path)
|
|
9234
|
+
};
|
|
9235
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
9236
|
+
}
|
|
9237
|
+
function isRecord(value) {
|
|
9238
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9239
|
+
}
|
|
9240
|
+
function stringField2(value) {
|
|
9241
|
+
return typeof value === "string" ? value : "";
|
|
9242
|
+
}
|
|
9243
|
+
function stringArray(value) {
|
|
9244
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
|
|
9245
|
+
}
|
|
9246
|
+
function wikilinkSlug2(value) {
|
|
9247
|
+
if (typeof value !== "string") return void 0;
|
|
9248
|
+
const match = value.match(/^\[\[([^\]]+)\]\]$/);
|
|
9249
|
+
return match?.[1] ?? value;
|
|
9250
|
+
}
|
|
9251
|
+
function summarize2(body) {
|
|
9252
|
+
const lines = body.split(/\r?\n/).map((line) => line.replace(/^#+\s*/, "").trim()).filter((line) => line.length > 0 && !line.startsWith("```"));
|
|
9253
|
+
const summary = lines.join(" ").replace(/\s+/g, " ").trim();
|
|
9254
|
+
return summary.length > 180 ? `${summary.slice(0, 177).trimEnd()}...` : summary;
|
|
9255
|
+
}
|
|
9256
|
+
function dateFromPath2(path) {
|
|
9257
|
+
return path.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? "";
|
|
9258
|
+
}
|
|
9259
|
+
function slugify3(value) {
|
|
9260
|
+
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9261
|
+
return slug || "memory";
|
|
9262
|
+
}
|
|
9263
|
+
|
|
9264
|
+
// src/commands/ingest.ts
|
|
9265
|
+
import { readFile as readFile27, writeFile as writeFile16, mkdir as mkdir15 } from "fs/promises";
|
|
9266
|
+
import { join as join40 } from "path";
|
|
9267
|
+
import { createHash as createHash9 } from "crypto";
|
|
8679
9268
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
8680
9269
|
var TYPE_DIR = {
|
|
8681
9270
|
entity: "entities",
|
|
@@ -8684,7 +9273,7 @@ var TYPE_DIR = {
|
|
|
8684
9273
|
query: "queries"
|
|
8685
9274
|
};
|
|
8686
9275
|
var ALLOWED_PROVENANCE = /* @__PURE__ */ new Set(["research", "project"]);
|
|
8687
|
-
function
|
|
9276
|
+
function slugify4(text) {
|
|
8688
9277
|
return text.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80) || "untitled";
|
|
8689
9278
|
}
|
|
8690
9279
|
function isUrl(source) {
|
|
@@ -8833,7 +9422,7 @@ async function runIngest(input) {
|
|
|
8833
9422
|
sourceContent = fetchResult.data.body;
|
|
8834
9423
|
} else {
|
|
8835
9424
|
try {
|
|
8836
|
-
sourceContent = await
|
|
9425
|
+
sourceContent = await readFile27(input.source, "utf8");
|
|
8837
9426
|
} catch {
|
|
8838
9427
|
return {
|
|
8839
9428
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -8851,15 +9440,15 @@ async function runIngest(input) {
|
|
|
8851
9440
|
})
|
|
8852
9441
|
};
|
|
8853
9442
|
}
|
|
8854
|
-
const sha256 =
|
|
9443
|
+
const sha256 = createHash9("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
8855
9444
|
const today = todayIso();
|
|
8856
|
-
const slug =
|
|
9445
|
+
const slug = slugify4(input.title);
|
|
8857
9446
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
8858
9447
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
8859
9448
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
8860
9449
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
8861
|
-
const rawAbsPath =
|
|
8862
|
-
const typedAbsPath =
|
|
9450
|
+
const rawAbsPath = join40(input.vault, rawRelPath);
|
|
9451
|
+
const typedAbsPath = join40(input.vault, typedRelPath);
|
|
8863
9452
|
const identity = assessSourceIdentity({
|
|
8864
9453
|
rawPath: rawRelPath,
|
|
8865
9454
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -8941,8 +9530,8 @@ async function runIngest(input) {
|
|
|
8941
9530
|
};
|
|
8942
9531
|
}
|
|
8943
9532
|
try {
|
|
8944
|
-
await
|
|
8945
|
-
await
|
|
9533
|
+
await mkdir15(join40(input.vault, "raw", "articles"), { recursive: true });
|
|
9534
|
+
await writeFile16(rawAbsPath, rawContent, "utf8");
|
|
8946
9535
|
} catch (e) {
|
|
8947
9536
|
return {
|
|
8948
9537
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -8950,8 +9539,8 @@ async function runIngest(input) {
|
|
|
8950
9539
|
};
|
|
8951
9540
|
}
|
|
8952
9541
|
try {
|
|
8953
|
-
await
|
|
8954
|
-
await
|
|
9542
|
+
await mkdir15(join40(input.vault, typedDir), { recursive: true });
|
|
9543
|
+
await writeFile16(typedAbsPath, typedContent, "utf8");
|
|
8955
9544
|
} catch (e) {
|
|
8956
9545
|
return {
|
|
8957
9546
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -9130,19 +9719,19 @@ ${body}`;
|
|
|
9130
9719
|
|
|
9131
9720
|
// src/commands/sync.ts
|
|
9132
9721
|
import { existsSync as existsSync17 } from "fs";
|
|
9133
|
-
import { join as
|
|
9722
|
+
import { join as join42 } from "path";
|
|
9134
9723
|
|
|
9135
9724
|
// src/utils/sync-lock.ts
|
|
9136
9725
|
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
9137
|
-
import { join as
|
|
9138
|
-
import { createHash as
|
|
9726
|
+
import { join as join41 } from "path";
|
|
9727
|
+
import { createHash as createHash10 } from "crypto";
|
|
9139
9728
|
function getSessionId() {
|
|
9140
9729
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
9141
9730
|
if (process.env.SKILLWIKI_SESSION_ID) return process.env.SKILLWIKI_SESSION_ID;
|
|
9142
9731
|
return process.pid.toString();
|
|
9143
9732
|
}
|
|
9144
9733
|
function lockPath(vault) {
|
|
9145
|
-
return
|
|
9734
|
+
return join41(vault, ".skillwiki", "sync.lock");
|
|
9146
9735
|
}
|
|
9147
9736
|
function readLock(vault) {
|
|
9148
9737
|
const path = lockPath(vault);
|
|
@@ -9161,7 +9750,7 @@ function isStale(lock, now) {
|
|
|
9161
9750
|
}
|
|
9162
9751
|
function acquireLock(vault, opts = {}) {
|
|
9163
9752
|
const path = lockPath(vault);
|
|
9164
|
-
const dir =
|
|
9753
|
+
const dir = join41(vault, ".skillwiki");
|
|
9165
9754
|
if (!existsSync16(dir)) {
|
|
9166
9755
|
mkdirSync6(dir, { recursive: true });
|
|
9167
9756
|
}
|
|
@@ -9232,11 +9821,22 @@ function releaseLock(vault, opts = {}) {
|
|
|
9232
9821
|
}
|
|
9233
9822
|
}
|
|
9234
9823
|
|
|
9824
|
+
// src/utils/vault-git-pathspec.ts
|
|
9825
|
+
var VAULT_GENERATED_COMMIT_PATHS = [
|
|
9826
|
+
".skillwiki/last-op.json",
|
|
9827
|
+
".skillwiki/memory",
|
|
9828
|
+
".skillwiki/memory-topics.json"
|
|
9829
|
+
];
|
|
9830
|
+
var VAULT_GENERATED_COMMIT_EXCLUDES = [
|
|
9831
|
+
...VAULT_GENERATED_COMMIT_PATHS.map((path) => `:!${path}`)
|
|
9832
|
+
];
|
|
9833
|
+
var VAULT_COMMIT_PATHSPEC = [".", ...VAULT_GENERATED_COMMIT_EXCLUDES];
|
|
9834
|
+
|
|
9235
9835
|
// src/commands/sync.ts
|
|
9236
9836
|
function runSyncStatus(input) {
|
|
9237
9837
|
const vault = input.vault;
|
|
9238
9838
|
const includeStashes = input.includeStashes ?? false;
|
|
9239
|
-
if (!existsSync17(
|
|
9839
|
+
if (!existsSync17(join42(vault, ".git"))) {
|
|
9240
9840
|
return {
|
|
9241
9841
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
9242
9842
|
result: ok({
|
|
@@ -9314,7 +9914,7 @@ function runSyncStatus(input) {
|
|
|
9314
9914
|
}
|
|
9315
9915
|
async function runSyncPush(input) {
|
|
9316
9916
|
const vault = input.vault;
|
|
9317
|
-
if (!existsSync17(
|
|
9917
|
+
if (!existsSync17(join42(vault, ".git"))) {
|
|
9318
9918
|
return {
|
|
9319
9919
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
9320
9920
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -9357,10 +9957,12 @@ async function runSyncPush(input) {
|
|
|
9357
9957
|
};
|
|
9358
9958
|
}
|
|
9359
9959
|
try {
|
|
9360
|
-
gitStrict(vault, ["add", "-A"]);
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9960
|
+
gitStrict(vault, ["add", "-A", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
9961
|
+
for (const generatedPath of VAULT_GENERATED_COMMIT_PATHS) {
|
|
9962
|
+
try {
|
|
9963
|
+
gitStrict(vault, ["reset", "HEAD", "--", generatedPath]);
|
|
9964
|
+
} catch (_e) {
|
|
9965
|
+
}
|
|
9364
9966
|
}
|
|
9365
9967
|
} catch (e) {
|
|
9366
9968
|
return {
|
|
@@ -9437,7 +10039,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
9437
10039
|
}
|
|
9438
10040
|
async function runSyncPull(input) {
|
|
9439
10041
|
const vault = input.vault;
|
|
9440
|
-
if (!existsSync17(
|
|
10042
|
+
if (!existsSync17(join42(vault, ".git"))) {
|
|
9441
10043
|
return {
|
|
9442
10044
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
9443
10045
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -9678,7 +10280,7 @@ function runSyncUnlock(input) {
|
|
|
9678
10280
|
|
|
9679
10281
|
// src/commands/backup.ts
|
|
9680
10282
|
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
9681
|
-
import { join as
|
|
10283
|
+
import { join as join43, relative as relative5, dirname as dirname14 } from "path";
|
|
9682
10284
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
9683
10285
|
|
|
9684
10286
|
// src/utils/s3-client.ts
|
|
@@ -9702,11 +10304,11 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node
|
|
|
9702
10304
|
function* walkMarkdown(dir, base) {
|
|
9703
10305
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
9704
10306
|
if (SKIP_DIRS2.has(entry.name)) continue;
|
|
9705
|
-
const full =
|
|
10307
|
+
const full = join43(dir, entry.name);
|
|
9706
10308
|
if (entry.isDirectory()) {
|
|
9707
10309
|
yield* walkMarkdown(full, base);
|
|
9708
10310
|
} else if (entry.name.endsWith(".md")) {
|
|
9709
|
-
yield
|
|
10311
|
+
yield relative5(base, full).replace(/\\/g, "/");
|
|
9710
10312
|
}
|
|
9711
10313
|
}
|
|
9712
10314
|
}
|
|
@@ -9725,7 +10327,7 @@ async function runBackupSync(input) {
|
|
|
9725
10327
|
let failed = 0;
|
|
9726
10328
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
9727
10329
|
for (const relPath of files) {
|
|
9728
|
-
const absPath =
|
|
10330
|
+
const absPath = join43(input.vault, relPath);
|
|
9729
10331
|
const localStat = statSync5(absPath);
|
|
9730
10332
|
let needsUpload = true;
|
|
9731
10333
|
try {
|
|
@@ -9801,7 +10403,7 @@ async function runBackupRestore(input) {
|
|
|
9801
10403
|
const objects = list.Contents ?? [];
|
|
9802
10404
|
for (const obj of objects) {
|
|
9803
10405
|
if (!obj.Key) continue;
|
|
9804
|
-
const localPath =
|
|
10406
|
+
const localPath = join43(target, obj.Key);
|
|
9805
10407
|
try {
|
|
9806
10408
|
const localStat = statSync5(localPath);
|
|
9807
10409
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -9848,8 +10450,8 @@ async function runBackupRestore(input) {
|
|
|
9848
10450
|
|
|
9849
10451
|
// src/commands/status.ts
|
|
9850
10452
|
import { existsSync as existsSync18, statSync as statSync6 } from "fs";
|
|
9851
|
-
import { readFile as
|
|
9852
|
-
import { join as
|
|
10453
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
10454
|
+
import { join as join44 } from "path";
|
|
9853
10455
|
async function runStatus(input) {
|
|
9854
10456
|
if (!existsSync18(input.vault)) {
|
|
9855
10457
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
@@ -9876,7 +10478,7 @@ async function runStatus(input) {
|
|
|
9876
10478
|
const compound = scan.data.compound.length;
|
|
9877
10479
|
let schemaVersion = "v1";
|
|
9878
10480
|
try {
|
|
9879
|
-
const schemaContent = await
|
|
10481
|
+
const schemaContent = await readFile28(join44(input.vault, "SCHEMA.md"), "utf8");
|
|
9880
10482
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
9881
10483
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
9882
10484
|
} catch {
|
|
@@ -9936,8 +10538,8 @@ async function runStatus(input) {
|
|
|
9936
10538
|
}
|
|
9937
10539
|
|
|
9938
10540
|
// src/commands/seed.ts
|
|
9939
|
-
import { mkdir as
|
|
9940
|
-
import { join as
|
|
10541
|
+
import { mkdir as mkdir16, writeFile as writeFile17, stat as stat10 } from "fs/promises";
|
|
10542
|
+
import { join as join45 } from "path";
|
|
9941
10543
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9942
10544
|
var EXAMPLE_PAGES = {
|
|
9943
10545
|
"entities/example-project.md": `---
|
|
@@ -10006,30 +10608,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
10006
10608
|
`;
|
|
10007
10609
|
async function runSeed(input) {
|
|
10008
10610
|
try {
|
|
10009
|
-
await
|
|
10611
|
+
await stat10(join45(input.vault, "SCHEMA.md"));
|
|
10010
10612
|
} catch {
|
|
10011
10613
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
10012
10614
|
}
|
|
10013
10615
|
const created = [];
|
|
10014
10616
|
const skipped = [];
|
|
10015
10617
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
10016
|
-
const absPath =
|
|
10618
|
+
const absPath = join45(input.vault, relPath);
|
|
10017
10619
|
try {
|
|
10018
|
-
await
|
|
10620
|
+
await stat10(absPath);
|
|
10019
10621
|
skipped.push(relPath);
|
|
10020
10622
|
} catch {
|
|
10021
|
-
await
|
|
10022
|
-
await
|
|
10623
|
+
await mkdir16(join45(absPath, ".."), { recursive: true });
|
|
10624
|
+
await writeFile17(absPath, content, "utf8");
|
|
10023
10625
|
created.push(relPath);
|
|
10024
10626
|
}
|
|
10025
10627
|
}
|
|
10026
|
-
const rawPath =
|
|
10628
|
+
const rawPath = join45(input.vault, "raw", "articles", "example-source.md");
|
|
10027
10629
|
try {
|
|
10028
|
-
await
|
|
10630
|
+
await stat10(rawPath);
|
|
10029
10631
|
skipped.push("raw/articles/example-source.md");
|
|
10030
10632
|
} catch {
|
|
10031
|
-
await
|
|
10032
|
-
await
|
|
10633
|
+
await mkdir16(join45(rawPath, ".."), { recursive: true });
|
|
10634
|
+
await writeFile17(rawPath, EXAMPLE_RAW, "utf8");
|
|
10033
10635
|
created.push("raw/articles/example-source.md");
|
|
10034
10636
|
}
|
|
10035
10637
|
if (created.length > 0) {
|
|
@@ -10051,9 +10653,9 @@ async function runSeed(input) {
|
|
|
10051
10653
|
}
|
|
10052
10654
|
|
|
10053
10655
|
// src/commands/canvas.ts
|
|
10054
|
-
import { readFile as
|
|
10656
|
+
import { readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
|
|
10055
10657
|
import { existsSync as existsSync19 } from "fs";
|
|
10056
|
-
import { join as
|
|
10658
|
+
import { join as join46 } from "path";
|
|
10057
10659
|
var NODE_WIDTH = 240;
|
|
10058
10660
|
var NODE_HEIGHT = 60;
|
|
10059
10661
|
var COLUMN_SPACING = 400;
|
|
@@ -10131,7 +10733,7 @@ function buildCanvasEdges(adjacency) {
|
|
|
10131
10733
|
return edges;
|
|
10132
10734
|
}
|
|
10133
10735
|
async function runCanvasGenerate(input) {
|
|
10134
|
-
const graphPath = input.graphPath ??
|
|
10736
|
+
const graphPath = input.graphPath ?? join46(input.vault, ".skillwiki", "graph.json");
|
|
10135
10737
|
if (!existsSync19(graphPath)) {
|
|
10136
10738
|
return {
|
|
10137
10739
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -10143,7 +10745,7 @@ async function runCanvasGenerate(input) {
|
|
|
10143
10745
|
}
|
|
10144
10746
|
let raw;
|
|
10145
10747
|
try {
|
|
10146
|
-
raw = await
|
|
10748
|
+
raw = await readFile29(graphPath, "utf8");
|
|
10147
10749
|
} catch (e) {
|
|
10148
10750
|
return {
|
|
10149
10751
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -10169,9 +10771,9 @@ async function runCanvasGenerate(input) {
|
|
|
10169
10771
|
const nodes = buildCanvasNodes(paths);
|
|
10170
10772
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
10171
10773
|
const canvas = { nodes, edges };
|
|
10172
|
-
const outPath =
|
|
10774
|
+
const outPath = join46(input.vault, "vault-graph.canvas");
|
|
10173
10775
|
try {
|
|
10174
|
-
await
|
|
10776
|
+
await writeFile18(outPath, JSON.stringify(canvas, null, 2));
|
|
10175
10777
|
} catch (e) {
|
|
10176
10778
|
return {
|
|
10177
10779
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -10191,8 +10793,8 @@ written: ${outPath}`
|
|
|
10191
10793
|
}
|
|
10192
10794
|
|
|
10193
10795
|
// src/commands/query.ts
|
|
10194
|
-
import { readFile as
|
|
10195
|
-
import { join as
|
|
10796
|
+
import { readFile as readFile30, stat as stat11 } from "fs/promises";
|
|
10797
|
+
import { join as join47 } from "path";
|
|
10196
10798
|
var W_KEYWORD = 2;
|
|
10197
10799
|
var W_SOURCE_OVERLAP = 4;
|
|
10198
10800
|
var W_WIKILINK = 3;
|
|
@@ -10313,10 +10915,10 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
10313
10915
|
return score;
|
|
10314
10916
|
}
|
|
10315
10917
|
async function loadOrBuildGraph(vault) {
|
|
10316
|
-
const graphPath =
|
|
10918
|
+
const graphPath = join47(vault, ".skillwiki", "graph.json");
|
|
10317
10919
|
let needsBuild = false;
|
|
10318
10920
|
try {
|
|
10319
|
-
const fileStat = await
|
|
10921
|
+
const fileStat = await stat11(graphPath);
|
|
10320
10922
|
const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
|
|
10321
10923
|
if (ageHours > 24) needsBuild = true;
|
|
10322
10924
|
} catch {
|
|
@@ -10327,7 +10929,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
10327
10929
|
if (buildResult.exitCode !== 0) return null;
|
|
10328
10930
|
}
|
|
10329
10931
|
try {
|
|
10330
|
-
const raw = await
|
|
10932
|
+
const raw = await readFile30(graphPath, "utf8");
|
|
10331
10933
|
return JSON.parse(raw);
|
|
10332
10934
|
} catch {
|
|
10333
10935
|
return null;
|
|
@@ -10336,24 +10938,26 @@ async function loadOrBuildGraph(vault) {
|
|
|
10336
10938
|
|
|
10337
10939
|
// src/utils/auto-commit.ts
|
|
10338
10940
|
import { existsSync as existsSync20 } from "fs";
|
|
10339
|
-
import { join as
|
|
10941
|
+
import { join as join48 } from "path";
|
|
10340
10942
|
async function postCommit(vault, exitCode) {
|
|
10341
10943
|
if (exitCode !== 0) return;
|
|
10342
10944
|
const home = process.env.HOME ?? "";
|
|
10343
10945
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
10344
10946
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
10345
10947
|
if (autoCommit === "false") return;
|
|
10346
|
-
if (!existsSync20(
|
|
10948
|
+
if (!existsSync20(join48(vault, ".git"))) return;
|
|
10347
10949
|
const lastOps = readLastOp(vault);
|
|
10348
10950
|
if (lastOps.length === 0) return;
|
|
10349
|
-
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
10951
|
+
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
10350
10952
|
if (!porcelain || porcelain.trim().length === 0) return;
|
|
10351
10953
|
const { gitStrict: gitStrict2 } = await import("./git-M4WGJ5G3.js");
|
|
10352
10954
|
try {
|
|
10353
|
-
gitStrict2(vault, ["add", "-A"]);
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10955
|
+
gitStrict2(vault, ["add", "-A", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
10956
|
+
for (const generatedPath of VAULT_GENERATED_COMMIT_PATHS) {
|
|
10957
|
+
try {
|
|
10958
|
+
gitStrict2(vault, ["reset", "HEAD", "--", generatedPath]);
|
|
10959
|
+
} catch (_e) {
|
|
10960
|
+
}
|
|
10357
10961
|
}
|
|
10358
10962
|
} catch (e) {
|
|
10359
10963
|
process.stderr.write(`auto-commit: git add failed: ${String(e)}
|
|
@@ -10394,7 +10998,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
10394
10998
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
10395
10999
|
});
|
|
10396
11000
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
10397
|
-
const out = opts.out ??
|
|
11001
|
+
const out = opts.out ?? join49(vault, ".skillwiki", "graph.json");
|
|
10398
11002
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
10399
11003
|
});
|
|
10400
11004
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -10763,6 +11367,45 @@ program.command("session-brief [vault]").description("render or refresh the boun
|
|
|
10763
11367
|
env: { SKILLWIKI_PROJECT: process.env.SKILLWIKI_PROJECT }
|
|
10764
11368
|
}), v.vault, { postCommit: !!opts.write });
|
|
10765
11369
|
});
|
|
11370
|
+
var memoryCmd = program.command("memory").description("inspect derived agent memory caches");
|
|
11371
|
+
memoryCmd.command("topics [vault]").description("list topic-oriented memory from the optional derived cache").option("--project <slug>", "filter topics by project slug").option("--limit <n>", "maximum topics to return", (s) => parseInt(s, 10), 10).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11372
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
11373
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
11374
|
+
else emit(await runMemoryTopics({
|
|
11375
|
+
vault: v.vault,
|
|
11376
|
+
project: opts.project,
|
|
11377
|
+
limit: opts.limit
|
|
11378
|
+
}), v.vault, { postCommit: false });
|
|
11379
|
+
});
|
|
11380
|
+
memoryCmd.command("index [vault]").description("build the derived project memory topic cache").requiredOption("--project <slug>", "project slug").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11381
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
11382
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
11383
|
+
else emit(await runMemoryIndex({
|
|
11384
|
+
vault: v.vault,
|
|
11385
|
+
project: opts.project
|
|
11386
|
+
}), v.vault);
|
|
11387
|
+
});
|
|
11388
|
+
memoryCmd.command("recall [vault]").description("recall bounded source summaries for a memory topic").requiredOption("--project <slug>", "project slug").requiredOption("--topic <slug>", "memory topic slug").option("--limit <n>", "maximum sources to return", (s) => parseInt(s, 10), 10).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11389
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
11390
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
11391
|
+
else emit(await runMemoryRecall({
|
|
11392
|
+
vault: v.vault,
|
|
11393
|
+
project: opts.project,
|
|
11394
|
+
topic: opts.topic,
|
|
11395
|
+
limit: opts.limit
|
|
11396
|
+
}), v.vault, { postCommit: false });
|
|
11397
|
+
});
|
|
11398
|
+
memoryCmd.command("import [vault]").description("preview or apply local memory imports into raw captures").requiredOption("--from <path>", "source file or directory to scan").requiredOption("--project <slug>", "project slug").option("--dry-run", "preview only; do not write captures", false).option("--apply", "write validated raw captures for ready entries", false).option("--max-bytes <n>", "reject source files above this size", (s) => parseInt(s, 10), 2e5).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11399
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
11400
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
11401
|
+
else emit(await runMemoryImport({
|
|
11402
|
+
vault: v.vault,
|
|
11403
|
+
from: opts.from,
|
|
11404
|
+
project: opts.project,
|
|
11405
|
+
apply: !!opts.apply && !opts.dryRun,
|
|
11406
|
+
maxBytes: opts.maxBytes
|
|
11407
|
+
}), v.vault, { postCommit: !!opts.apply && !opts.dryRun });
|
|
11408
|
+
});
|
|
10766
11409
|
program.command("ingest <source>").description("ingest a source URL or local file into the vault").requiredOption("--vault <path>", "vault root directory").requiredOption("--type <type>", "typed-knowledge type (entity|concept|comparison|query)").requiredOption("--title <title>", "page title").option("--tags <csv>", "comma-separated tags").option("--provenance <provenance>", "provenance (research|project)").option("--dry-run", "preview without writing files", false).action(async (source, opts) => {
|
|
10767
11410
|
const tags = typeof opts.tags === "string" ? opts.tags.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
|
|
10768
11411
|
emit(await runIngest({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 18 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|