skillwiki 0.9.19 → 0.9.21
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 +260 -42
- 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
|
@@ -2866,9 +2866,9 @@ ${content}
|
|
|
2866
2866
|
|
|
2867
2867
|
// src/commands/lint.ts
|
|
2868
2868
|
import { existsSync as existsSync6 } from "fs";
|
|
2869
|
-
import { readFile as readFile17 } from "fs/promises";
|
|
2869
|
+
import { readFile as readFile17, readdir as readdir5 } from "fs/promises";
|
|
2870
2870
|
import { createHash as createHash5 } from "crypto";
|
|
2871
|
-
import { join as join23 } from "path";
|
|
2871
|
+
import { join as join23, relative as relative3, sep as sep3 } from "path";
|
|
2872
2872
|
|
|
2873
2873
|
// src/commands/sparse-community.ts
|
|
2874
2874
|
async function runSparseCommunity(input) {
|
|
@@ -3527,8 +3527,8 @@ function buildCliSurface() {
|
|
|
3527
3527
|
backupCmd2.command("restore").option("--bucket <name>").option("--endpoint <url>").option("--region <region>").option("--target <dir>").option("--wiki <name>");
|
|
3528
3528
|
const memoryCmd2 = program2.commands.find((c) => c.name() === "memory");
|
|
3529
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>");
|
|
3530
|
+
memoryCmd2.command("index").requiredOption("--project <slug>").option("--check").option("--if-stale").option("--wiki <name>");
|
|
3531
|
+
memoryCmd2.command("recall").requiredOption("--project <slug>").requiredOption("--topic <slug>").option("--scope <scope>").option("--limit <n>").option("--wiki <name>");
|
|
3532
3532
|
memoryCmd2.command("import").requiredOption("--from <path>").requiredOption("--project <slug>").option("--dry-run").option("--apply").option("--max-bytes <n>").option("--wiki <name>");
|
|
3533
3533
|
const fleetCmd2 = program2.commands.find((c) => c.name() === "fleet");
|
|
3534
3534
|
fleetCmd2.command("validate");
|
|
@@ -3714,6 +3714,7 @@ var ERROR_ORDER = ["sensitive_content", "broken_wikilinks", "invalid_frontmatter
|
|
|
3714
3714
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
3715
3715
|
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3716
3716
|
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
3717
|
+
var CLI_REFS_TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
3717
3718
|
function shellQuote(value) {
|
|
3718
3719
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3719
3720
|
}
|
|
@@ -3786,6 +3787,63 @@ function summarizeLintOutput(output, examplesLimit = 3) {
|
|
|
3786
3787
|
humanHint: lines.join("\n")
|
|
3787
3788
|
};
|
|
3788
3789
|
}
|
|
3790
|
+
async function walkMarkdownFiles(absDir, vaultRoot) {
|
|
3791
|
+
const entries = await readdir5(absDir, { withFileTypes: true });
|
|
3792
|
+
const pages = [];
|
|
3793
|
+
for (const entry of entries) {
|
|
3794
|
+
const absPath = join23(absDir, entry.name);
|
|
3795
|
+
if (entry.isDirectory()) {
|
|
3796
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
3797
|
+
pages.push(...await walkMarkdownFiles(absPath, vaultRoot));
|
|
3798
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3799
|
+
pages.push({ absPath, relPath: relative3(vaultRoot, absPath).split(sep3).join("/") });
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
return pages;
|
|
3803
|
+
}
|
|
3804
|
+
async function collectCliRefsPages(vault) {
|
|
3805
|
+
if (!existsSync6(join23(vault, "SCHEMA.md"))) {
|
|
3806
|
+
return err("VAULT_PATH_INVALID", { root: vault, reason: "SCHEMA.md missing" });
|
|
3807
|
+
}
|
|
3808
|
+
const pages = [];
|
|
3809
|
+
for (const dir of CLI_REFS_TYPED_DIRS) {
|
|
3810
|
+
const absDir = join23(vault, dir);
|
|
3811
|
+
if (!existsSync6(absDir)) continue;
|
|
3812
|
+
pages.push(...await walkMarkdownFiles(absDir, vault));
|
|
3813
|
+
}
|
|
3814
|
+
return ok(pages);
|
|
3815
|
+
}
|
|
3816
|
+
async function runCliRefsOnly(input) {
|
|
3817
|
+
const pages = await collectCliRefsPages(input.vault);
|
|
3818
|
+
if (!pages.ok) {
|
|
3819
|
+
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: pages };
|
|
3820
|
+
}
|
|
3821
|
+
const cliRefFlags = [];
|
|
3822
|
+
const cliSurface = buildCliSurface();
|
|
3823
|
+
for (const page of pages.data) {
|
|
3824
|
+
const text = await readPage(page);
|
|
3825
|
+
const violations = validateCliRefs(text, page.relPath, cliSurface);
|
|
3826
|
+
for (const v of violations) {
|
|
3827
|
+
cliRefFlags.push(`${v.page}: ${v.ref} (${v.reason})`);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
const infoOut = cliRefFlags.length > 0 ? [{ kind: "cli_refs", items: cliRefFlags }] : [];
|
|
3831
|
+
const summary = { errors: 0, warnings: 0, info: cliRefFlags.length };
|
|
3832
|
+
const exitCode = cliRefFlags.length > 0 ? ExitCode.LINT_HAS_WARNINGS : ExitCode.OK;
|
|
3833
|
+
const output = {
|
|
3834
|
+
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3835
|
+
summary,
|
|
3836
|
+
by_severity: { error: [], warning: [], info: infoOut },
|
|
3837
|
+
fixed: [],
|
|
3838
|
+
unresolved: [],
|
|
3839
|
+
humanHint: `--only cli_refs
|
|
3840
|
+
${cliRefFlags.length === 0 ? "0 violations" : ` cli_refs: ${cliRefFlags.length}`}`
|
|
3841
|
+
};
|
|
3842
|
+
return {
|
|
3843
|
+
exitCode,
|
|
3844
|
+
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3789
3847
|
async function runLint(input) {
|
|
3790
3848
|
if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
|
|
3791
3849
|
return {
|
|
@@ -3793,6 +3851,9 @@ async function runLint(input) {
|
|
|
3793
3851
|
result: { ok: false, error: "UNKNOWN_BUCKET", detail: `Unknown bucket "${input.only}". Valid: ${KNOWN_BUCKETS.join(", ")}` }
|
|
3794
3852
|
};
|
|
3795
3853
|
}
|
|
3854
|
+
if (input.only === "cli_refs") {
|
|
3855
|
+
return runCliRefsOnly(input);
|
|
3856
|
+
}
|
|
3796
3857
|
const shouldFix = (bucket) => !!input.fix && (!input.only || input.only === bucket);
|
|
3797
3858
|
const buckets = {};
|
|
3798
3859
|
const fixed = [];
|
|
@@ -8001,13 +8062,13 @@ async function runSelfUpdate(input) {
|
|
|
8001
8062
|
}
|
|
8002
8063
|
|
|
8003
8064
|
// src/commands/transcripts.ts
|
|
8004
|
-
import { readdir as
|
|
8065
|
+
import { readdir as readdir6, stat as stat8, readFile as readFile22 } from "fs/promises";
|
|
8005
8066
|
import { join as join34 } from "path";
|
|
8006
8067
|
async function runTranscripts(input) {
|
|
8007
8068
|
const dir = join34(input.vault, "raw", "transcripts");
|
|
8008
8069
|
let entries;
|
|
8009
8070
|
try {
|
|
8010
|
-
entries = await
|
|
8071
|
+
entries = await readdir6(dir, { withFileTypes: true });
|
|
8011
8072
|
} catch {
|
|
8012
8073
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: { ok: false, error: "VAULT_PATH_INVALID", detail: `raw/transcripts/ not found: ${dir}` } };
|
|
8013
8074
|
}
|
|
@@ -8032,14 +8093,14 @@ async function runTranscripts(input) {
|
|
|
8032
8093
|
}
|
|
8033
8094
|
|
|
8034
8095
|
// src/commands/project-index.ts
|
|
8035
|
-
import { readdir as
|
|
8096
|
+
import { readdir as readdir7, readFile as readFile23, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
8036
8097
|
import { join as join35, dirname as dirname13 } from "path";
|
|
8037
8098
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
8038
8099
|
async function runProjectIndex(input) {
|
|
8039
8100
|
const slug = input.slug;
|
|
8040
8101
|
const projectDir = join35(input.vault, "projects", slug);
|
|
8041
8102
|
try {
|
|
8042
|
-
await
|
|
8103
|
+
await readdir7(projectDir);
|
|
8043
8104
|
} catch {
|
|
8044
8105
|
return {
|
|
8045
8106
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
@@ -8050,7 +8111,7 @@ async function runProjectIndex(input) {
|
|
|
8050
8111
|
const entries = [];
|
|
8051
8112
|
const compoundDir = join35(input.vault, "projects", slug, "compound");
|
|
8052
8113
|
try {
|
|
8053
|
-
const compoundFiles = await
|
|
8114
|
+
const compoundFiles = await readdir7(compoundDir, { withFileTypes: true });
|
|
8054
8115
|
for (const entry of compoundFiles) {
|
|
8055
8116
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
8056
8117
|
const filePath = join35(compoundDir, entry.name);
|
|
@@ -8073,7 +8134,7 @@ async function runProjectIndex(input) {
|
|
|
8073
8134
|
for (const dir of LAYER2_DIRS) {
|
|
8074
8135
|
let files;
|
|
8075
8136
|
try {
|
|
8076
|
-
files = await
|
|
8137
|
+
files = await readdir7(join35(input.vault, dir), { withFileTypes: true });
|
|
8077
8138
|
} catch {
|
|
8078
8139
|
continue;
|
|
8079
8140
|
}
|
|
@@ -8176,7 +8237,7 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
8176
8237
|
}
|
|
8177
8238
|
|
|
8178
8239
|
// src/commands/compound.ts
|
|
8179
|
-
import { writeFile as writeFile12, mkdir as mkdir11, readdir as
|
|
8240
|
+
import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir8, unlink as unlink4 } from "fs/promises";
|
|
8180
8241
|
import { join as join36 } from "path";
|
|
8181
8242
|
import { existsSync as existsSync14 } from "fs";
|
|
8182
8243
|
import { readFile as readFile24 } from "fs/promises";
|
|
@@ -8413,7 +8474,7 @@ no compound directory found`
|
|
|
8413
8474
|
}
|
|
8414
8475
|
let dirents;
|
|
8415
8476
|
try {
|
|
8416
|
-
dirents = await
|
|
8477
|
+
dirents = await readdir8(compoundDir, { withFileTypes: true });
|
|
8417
8478
|
} catch {
|
|
8418
8479
|
return {
|
|
8419
8480
|
exitCode: ExitCode.OK,
|
|
@@ -8545,7 +8606,7 @@ ${input.text.trim()}
|
|
|
8545
8606
|
|
|
8546
8607
|
// src/commands/session-brief.ts
|
|
8547
8608
|
import { mkdir as mkdir13, readFile as readFile25, writeFile as writeFile14 } from "fs/promises";
|
|
8548
|
-
import { join as join38, relative as
|
|
8609
|
+
import { join as join38, relative as relative4, sep as sep4 } from "path";
|
|
8549
8610
|
var MAX_WORDS = 900;
|
|
8550
8611
|
async function runSessionBrief(input) {
|
|
8551
8612
|
const scan = await scanVault(input.vault);
|
|
@@ -8649,7 +8710,7 @@ async function readProjectSlug(file) {
|
|
|
8649
8710
|
return void 0;
|
|
8650
8711
|
}
|
|
8651
8712
|
function inferProjectFromPath(vault, cwd) {
|
|
8652
|
-
const rel =
|
|
8713
|
+
const rel = relative4(vault, cwd).split(sep4).join("/");
|
|
8653
8714
|
const match = rel.match(/^projects\/([^/]+)(?:\/|$)/);
|
|
8654
8715
|
return match?.[1];
|
|
8655
8716
|
}
|
|
@@ -8938,8 +8999,8 @@ function dateFromPath(path) {
|
|
|
8938
8999
|
|
|
8939
9000
|
// src/commands/memory.ts
|
|
8940
9001
|
import { createHash as createHash8 } from "crypto";
|
|
8941
|
-
import { mkdir as mkdir14, readFile as readFile26, readdir as
|
|
8942
|
-
import { basename as basename2, extname, join as join39, relative as
|
|
9002
|
+
import { mkdir as mkdir14, readFile as readFile26, readdir as readdir9, stat as stat9, writeFile as writeFile15 } from "fs/promises";
|
|
9003
|
+
import { basename as basename2, extname, join as join39, relative as relative5, sep as sep5 } from "path";
|
|
8943
9004
|
async function runMemoryTopics(input) {
|
|
8944
9005
|
const scan = await scanVault(input.vault);
|
|
8945
9006
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -8983,24 +9044,34 @@ async function runMemoryTopics(input) {
|
|
|
8983
9044
|
async function runMemoryIndex(input) {
|
|
8984
9045
|
const scan = await scanVault(input.vault);
|
|
8985
9046
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
8986
|
-
const
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
9047
|
+
const state = await buildMemoryIndexState(scan.data.allMarkdown, input.project);
|
|
9048
|
+
let status;
|
|
9049
|
+
if (input.check || input.ifStale) {
|
|
9050
|
+
const checked = await checkMemoryIndex(input.vault, input.project, state);
|
|
9051
|
+
if (!checked.ok) return checked.error;
|
|
9052
|
+
status = checked.data;
|
|
9053
|
+
if (input.check || !status.stale) {
|
|
9054
|
+
return {
|
|
9055
|
+
exitCode: ExitCode.OK,
|
|
9056
|
+
result: ok({
|
|
9057
|
+
...status,
|
|
9058
|
+
files_written: [],
|
|
9059
|
+
warnings: state.warnings,
|
|
9060
|
+
humanHint: renderMemoryIndexStatusHint(status)
|
|
9061
|
+
})
|
|
9062
|
+
};
|
|
9063
|
+
}
|
|
8992
9064
|
}
|
|
8993
9065
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8994
|
-
const topics = buildTopics(input.project, sources);
|
|
8995
9066
|
const relCachePath = memoryCacheRelPath(input.project);
|
|
8996
9067
|
const absCachePath = join39(input.vault, relCachePath);
|
|
8997
9068
|
await mkdir14(join39(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
|
|
8998
9069
|
await writeFile15(absCachePath, `${JSON.stringify({
|
|
8999
9070
|
generated_at: generatedAt,
|
|
9000
9071
|
project: input.project,
|
|
9001
|
-
topics,
|
|
9002
|
-
sources,
|
|
9003
|
-
warnings
|
|
9072
|
+
topics: state.topics,
|
|
9073
|
+
sources: state.sources,
|
|
9074
|
+
warnings: state.warnings
|
|
9004
9075
|
}, null, 2)}
|
|
9005
9076
|
`, "utf8");
|
|
9006
9077
|
return {
|
|
@@ -9008,18 +9079,31 @@ async function runMemoryIndex(input) {
|
|
|
9008
9079
|
result: ok({
|
|
9009
9080
|
project: input.project,
|
|
9010
9081
|
generated_at: generatedAt,
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9082
|
+
cache_present: status?.cache_present ?? true,
|
|
9083
|
+
stale: status?.stale ?? false,
|
|
9084
|
+
drift: status?.drift ?? emptyMemoryIndexDrift(),
|
|
9085
|
+
topic_count: state.topics.length,
|
|
9086
|
+
source_count: state.sources.length,
|
|
9087
|
+
topics: state.topics,
|
|
9014
9088
|
files_written: [relCachePath],
|
|
9015
|
-
warnings,
|
|
9016
|
-
humanHint: `indexed ${sources.length} memory sources into ${topics.length} topics for ${input.project}`
|
|
9089
|
+
warnings: state.warnings,
|
|
9090
|
+
humanHint: `indexed ${state.sources.length} memory sources into ${state.topics.length} topics for ${input.project}`
|
|
9017
9091
|
})
|
|
9018
9092
|
};
|
|
9019
9093
|
}
|
|
9020
9094
|
async function runMemoryRecall(input) {
|
|
9021
9095
|
const scan = await scanVault(input.vault);
|
|
9022
9096
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
9097
|
+
const scope = normalizeRecallScope(input.scope);
|
|
9098
|
+
if (input.scope && !scope) {
|
|
9099
|
+
return {
|
|
9100
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
9101
|
+
result: err("WRITE_FAILED", {
|
|
9102
|
+
path: "memory recall --scope",
|
|
9103
|
+
message: `invalid memory recall scope: ${String(input.scope)}`
|
|
9104
|
+
})
|
|
9105
|
+
};
|
|
9106
|
+
}
|
|
9023
9107
|
const cacheText = await readMemoryCache(input.vault, input.project);
|
|
9024
9108
|
if (!cacheText) {
|
|
9025
9109
|
return {
|
|
@@ -9027,6 +9111,7 @@ async function runMemoryRecall(input) {
|
|
|
9027
9111
|
result: ok({
|
|
9028
9112
|
project: input.project,
|
|
9029
9113
|
topic: input.topic,
|
|
9114
|
+
...scope ? { scope } : {},
|
|
9030
9115
|
sources: [],
|
|
9031
9116
|
humanHint: `no memory topics cache found for project ${input.project}`
|
|
9032
9117
|
})
|
|
@@ -9045,12 +9130,13 @@ async function runMemoryRecall(input) {
|
|
|
9045
9130
|
};
|
|
9046
9131
|
}
|
|
9047
9132
|
const limit = normalizeLimit(input.limit);
|
|
9048
|
-
const sources = normalizeSources(parsed.sources).filter((source) => source.topics.includes(input.topic)).filter((source) => source.memory_privacy !== "secret-blocked").sort(
|
|
9133
|
+
const sources = normalizeSources(parsed.sources).filter((source) => source.topics.includes(input.topic)).filter((source) => source.memory_privacy !== "secret-blocked").filter((source) => source.memory_status !== "archived" && source.memory_status !== "rejected").filter((source) => recallScopeMatches(source, input.project, scope)).sort((a, b) => compareRecallSources(a, b, input.project, scope)).slice(0, limit);
|
|
9049
9134
|
return {
|
|
9050
9135
|
exitCode: ExitCode.OK,
|
|
9051
9136
|
result: ok({
|
|
9052
9137
|
project: input.project,
|
|
9053
9138
|
topic: input.topic,
|
|
9139
|
+
...scope ? { scope } : {},
|
|
9054
9140
|
sources,
|
|
9055
9141
|
humanHint: renderRecallHint(input.topic, sources)
|
|
9056
9142
|
})
|
|
@@ -9111,6 +9197,115 @@ async function runMemoryImport(input) {
|
|
|
9111
9197
|
})
|
|
9112
9198
|
};
|
|
9113
9199
|
}
|
|
9200
|
+
async function buildMemoryIndexState(pages, project) {
|
|
9201
|
+
const warnings = [];
|
|
9202
|
+
const sourcePages = dedupePages(pages);
|
|
9203
|
+
const sources = [];
|
|
9204
|
+
for (const page of sourcePages) {
|
|
9205
|
+
const source = await readMemorySource(page, project, warnings);
|
|
9206
|
+
if (source) sources.push(source);
|
|
9207
|
+
}
|
|
9208
|
+
return {
|
|
9209
|
+
sources,
|
|
9210
|
+
topics: buildTopics(project, sources),
|
|
9211
|
+
warnings
|
|
9212
|
+
};
|
|
9213
|
+
}
|
|
9214
|
+
async function checkMemoryIndex(vault, project, current) {
|
|
9215
|
+
const relCachePath = memoryCacheRelPath(project);
|
|
9216
|
+
const cacheText = await readIfExists3(join39(vault, relCachePath));
|
|
9217
|
+
if (!cacheText) {
|
|
9218
|
+
return {
|
|
9219
|
+
ok: true,
|
|
9220
|
+
data: {
|
|
9221
|
+
project,
|
|
9222
|
+
cache_present: false,
|
|
9223
|
+
stale: true,
|
|
9224
|
+
topic_count: current.topics.length,
|
|
9225
|
+
source_count: current.sources.length,
|
|
9226
|
+
topics: current.topics,
|
|
9227
|
+
drift: emptyMemoryIndexDrift(),
|
|
9228
|
+
files_written: [],
|
|
9229
|
+
warnings: current.warnings,
|
|
9230
|
+
humanHint: ""
|
|
9231
|
+
}
|
|
9232
|
+
};
|
|
9233
|
+
}
|
|
9234
|
+
let parsed;
|
|
9235
|
+
try {
|
|
9236
|
+
parsed = JSON.parse(cacheText);
|
|
9237
|
+
} catch (e) {
|
|
9238
|
+
return {
|
|
9239
|
+
ok: false,
|
|
9240
|
+
error: {
|
|
9241
|
+
exitCode: ExitCode.WRITE_FAILED,
|
|
9242
|
+
result: err("WRITE_FAILED", {
|
|
9243
|
+
path: relCachePath,
|
|
9244
|
+
message: `invalid memory topics cache: ${String(e)}`
|
|
9245
|
+
})
|
|
9246
|
+
}
|
|
9247
|
+
};
|
|
9248
|
+
}
|
|
9249
|
+
const cachedSources = normalizeSources(parsed.sources);
|
|
9250
|
+
const cachedTopics = normalizeTopics(parsed.topics);
|
|
9251
|
+
const drift = compareMemorySources(current.sources, cachedSources);
|
|
9252
|
+
const stale = drift.missing_sources.length > 0 || drift.removed_sources.length > 0 || drift.changed_sources.length > 0 || !sameStringSet(current.topics.map((topic) => topic.name), cachedTopics.map((topic) => topic.name));
|
|
9253
|
+
return {
|
|
9254
|
+
ok: true,
|
|
9255
|
+
data: {
|
|
9256
|
+
project,
|
|
9257
|
+
generated_at: stringField2(parsed.generated_at) || void 0,
|
|
9258
|
+
cache_present: true,
|
|
9259
|
+
stale,
|
|
9260
|
+
topic_count: current.topics.length,
|
|
9261
|
+
source_count: current.sources.length,
|
|
9262
|
+
topics: current.topics,
|
|
9263
|
+
drift,
|
|
9264
|
+
files_written: [],
|
|
9265
|
+
warnings: current.warnings,
|
|
9266
|
+
humanHint: ""
|
|
9267
|
+
}
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9270
|
+
function compareMemorySources(current, cached) {
|
|
9271
|
+
const currentByPath = new Map(current.map((source) => [source.path, source]));
|
|
9272
|
+
const cachedByPath = new Map(cached.map((source) => [source.path, source]));
|
|
9273
|
+
const missing_sources = current.filter((source) => !cachedByPath.has(source.path)).map((source) => source.path).sort((a, b) => a.localeCompare(b));
|
|
9274
|
+
const removed_sources = cached.filter((source) => !currentByPath.has(source.path)).map((source) => source.path).sort((a, b) => a.localeCompare(b));
|
|
9275
|
+
const changed_sources = current.filter((source) => {
|
|
9276
|
+
const cachedSource = cachedByPath.get(source.path);
|
|
9277
|
+
return !!cachedSource && !sameMemorySource(source, cachedSource);
|
|
9278
|
+
}).map((source) => source.path).sort((a, b) => a.localeCompare(b));
|
|
9279
|
+
return { missing_sources, removed_sources, changed_sources };
|
|
9280
|
+
}
|
|
9281
|
+
function sameMemorySource(a, b) {
|
|
9282
|
+
return a.hash === b.hash && a.updated === b.updated && (a.project ?? "") === (b.project ?? "") && (a.memory_kind ?? "") === (b.memory_kind ?? "") && (a.memory_scope ?? "") === (b.memory_scope ?? "") && (a.memory_policy ?? "") === (b.memory_policy ?? "") && a.memory_privacy === b.memory_privacy && a.memory_status === b.memory_status && sameStringSet(a.topics, b.topics);
|
|
9283
|
+
}
|
|
9284
|
+
function sameStringSet(a, b) {
|
|
9285
|
+
if (a.length !== b.length) return false;
|
|
9286
|
+
const left = [...a].sort((x, y) => x.localeCompare(y));
|
|
9287
|
+
const right = [...b].sort((x, y) => x.localeCompare(y));
|
|
9288
|
+
return left.every((value, index) => value === right[index]);
|
|
9289
|
+
}
|
|
9290
|
+
function emptyMemoryIndexDrift() {
|
|
9291
|
+
return {
|
|
9292
|
+
missing_sources: [],
|
|
9293
|
+
removed_sources: [],
|
|
9294
|
+
changed_sources: []
|
|
9295
|
+
};
|
|
9296
|
+
}
|
|
9297
|
+
function renderMemoryIndexStatusHint(status) {
|
|
9298
|
+
if (!status.cache_present) {
|
|
9299
|
+
return `memory index cache missing for ${status.project}; rebuild with memory index --project ${status.project}`;
|
|
9300
|
+
}
|
|
9301
|
+
if (!status.stale) {
|
|
9302
|
+
return `memory index cache current for ${status.project}: ${status.source_count} sources, ${status.topic_count} topics`;
|
|
9303
|
+
}
|
|
9304
|
+
const changed = status.drift.changed_sources.length;
|
|
9305
|
+
const missing = status.drift.missing_sources.length;
|
|
9306
|
+
const removed = status.drift.removed_sources.length;
|
|
9307
|
+
return `memory index cache stale for ${status.project}: ${missing} missing, ${removed} removed, ${changed} changed sources`;
|
|
9308
|
+
}
|
|
9114
9309
|
async function readIfExists3(path) {
|
|
9115
9310
|
try {
|
|
9116
9311
|
return await readFile26(path, "utf8");
|
|
@@ -9126,7 +9321,7 @@ async function collectImportFiles(source) {
|
|
|
9126
9321
|
return files.sort((a, b) => a.localeCompare(b));
|
|
9127
9322
|
}
|
|
9128
9323
|
async function walkImportFiles(dir, out) {
|
|
9129
|
-
const entries = await
|
|
9324
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
9130
9325
|
for (const entry of entries) {
|
|
9131
9326
|
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
9132
9327
|
const path = join39(dir, entry.name);
|
|
@@ -9179,7 +9374,7 @@ async function buildImportEntry(file, sourceRoot, project, today, maxBytes) {
|
|
|
9179
9374
|
const redacted = redactSensitiveContent(extracted, { file });
|
|
9180
9375
|
const privacy = redacted.findings.length > 0 ? "sensitive" : "local";
|
|
9181
9376
|
const sourceSlug = slugify3(basename2(file, extname(file)));
|
|
9182
|
-
const relSource =
|
|
9377
|
+
const relSource = relative5(sourceRoot, file).split(sep5).join("/");
|
|
9183
9378
|
const entry = {
|
|
9184
9379
|
...baseEntry,
|
|
9185
9380
|
status: "ready",
|
|
@@ -9251,7 +9446,7 @@ function hiddenString(entry, key) {
|
|
|
9251
9446
|
return entry[key] ?? "";
|
|
9252
9447
|
}
|
|
9253
9448
|
function classifyImportSource(file) {
|
|
9254
|
-
const rel = file.split(
|
|
9449
|
+
const rel = file.split(sep5).join("/");
|
|
9255
9450
|
const name = basename2(file);
|
|
9256
9451
|
if (rel.includes("/.codex/memories/")) return "codex-memory";
|
|
9257
9452
|
if (rel.includes("/.codex/rules/")) return "codex-rule";
|
|
@@ -9340,6 +9535,26 @@ function compareTopics(a, b) {
|
|
|
9340
9535
|
function compareSources(a, b) {
|
|
9341
9536
|
return b.updated.localeCompare(a.updated) || a.path.localeCompare(b.path);
|
|
9342
9537
|
}
|
|
9538
|
+
function normalizeRecallScope(value) {
|
|
9539
|
+
if (!value) return void 0;
|
|
9540
|
+
return isRecallScope(value) ? value : void 0;
|
|
9541
|
+
}
|
|
9542
|
+
function isRecallScope(value) {
|
|
9543
|
+
return value === "project" || value === "global" || value === "cross-agent" || value === "all";
|
|
9544
|
+
}
|
|
9545
|
+
function recallScopeMatches(source, project, scope) {
|
|
9546
|
+
if (!scope || scope === "all") return true;
|
|
9547
|
+
if (scope === "project") return isProjectMemorySource(source, project);
|
|
9548
|
+
return source.memory_scope === scope;
|
|
9549
|
+
}
|
|
9550
|
+
function compareRecallSources(a, b, project, scope) {
|
|
9551
|
+
if (scope !== "all") return compareSources(a, b);
|
|
9552
|
+
return b.updated.localeCompare(a.updated) || Number(isProjectMemorySource(b, project)) - Number(isProjectMemorySource(a, project)) || a.path.localeCompare(b.path);
|
|
9553
|
+
}
|
|
9554
|
+
function isProjectMemorySource(source, project) {
|
|
9555
|
+
const scope = source.memory_scope || "project";
|
|
9556
|
+
return scope === "project" && (!source.project || source.project === project);
|
|
9557
|
+
}
|
|
9343
9558
|
function renderHumanHint(topics) {
|
|
9344
9559
|
if (topics.length === 0) return "no memory topics found";
|
|
9345
9560
|
return topics.map((topic) => {
|
|
@@ -10498,7 +10713,7 @@ function runSyncUnlock(input) {
|
|
|
10498
10713
|
|
|
10499
10714
|
// src/commands/backup.ts
|
|
10500
10715
|
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
10501
|
-
import { join as join43, relative as
|
|
10716
|
+
import { join as join43, relative as relative6, dirname as dirname14 } from "path";
|
|
10502
10717
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
10503
10718
|
|
|
10504
10719
|
// src/utils/s3-client.ts
|
|
@@ -10526,7 +10741,7 @@ function* walkMarkdown(dir, base) {
|
|
|
10526
10741
|
if (entry.isDirectory()) {
|
|
10527
10742
|
yield* walkMarkdown(full, base);
|
|
10528
10743
|
} else if (entry.name.endsWith(".md")) {
|
|
10529
|
-
yield
|
|
10744
|
+
yield relative6(base, full).replace(/\\/g, "/");
|
|
10530
10745
|
}
|
|
10531
10746
|
}
|
|
10532
10747
|
}
|
|
@@ -11170,7 +11385,7 @@ async function postCommit(vault, exitCode) {
|
|
|
11170
11385
|
if (!porcelain || porcelain.trim().length === 0) return;
|
|
11171
11386
|
const { gitStrict: gitStrict2 } = await import("./git-M4WGJ5G3.js");
|
|
11172
11387
|
try {
|
|
11173
|
-
gitStrict2(vault, ["add", "-A", "--",
|
|
11388
|
+
gitStrict2(vault, ["add", "-A", "--", "."]);
|
|
11174
11389
|
for (const generatedPath of VAULT_GENERATED_COMMIT_PATHS) {
|
|
11175
11390
|
try {
|
|
11176
11391
|
gitStrict2(vault, ["reset", "HEAD", "--", generatedPath]);
|
|
@@ -11595,21 +11810,24 @@ memoryCmd.command("topics [vault]").description("list topic-oriented memory from
|
|
|
11595
11810
|
limit: opts.limit
|
|
11596
11811
|
}), v.vault, { postCommit: false });
|
|
11597
11812
|
});
|
|
11598
|
-
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) => {
|
|
11813
|
+
memoryCmd.command("index [vault]").description("build the derived project memory topic cache").requiredOption("--project <slug>", "project slug").option("--check", "report whether the local project memory cache is missing or stale without writing", false).option("--if-stale", "rebuild the local project memory cache only when it is missing or stale", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11599
11814
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
11600
11815
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
11601
11816
|
else emit(await runMemoryIndex({
|
|
11602
11817
|
vault: v.vault,
|
|
11603
|
-
project: opts.project
|
|
11604
|
-
|
|
11818
|
+
project: opts.project,
|
|
11819
|
+
check: !!opts.check,
|
|
11820
|
+
ifStale: !!opts.ifStale
|
|
11821
|
+
}), v.vault, { postCommit: !opts.check });
|
|
11605
11822
|
});
|
|
11606
|
-
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) => {
|
|
11823
|
+
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("--scope <scope>", "memory scope: project, global, cross-agent, or all").option("--limit <n>", "maximum sources to return", (s) => parseInt(s, 10), 10).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11607
11824
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
11608
11825
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
11609
11826
|
else emit(await runMemoryRecall({
|
|
11610
11827
|
vault: v.vault,
|
|
11611
11828
|
project: opts.project,
|
|
11612
11829
|
topic: opts.topic,
|
|
11830
|
+
scope: opts.scope,
|
|
11613
11831
|
limit: opts.limit
|
|
11614
11832
|
}), v.vault, { postCommit: false });
|
|
11615
11833
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.21",
|
|
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": {
|