skillwiki 0.9.20 → 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 CHANGED
@@ -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");
@@ -9044,24 +9044,34 @@ async function runMemoryTopics(input) {
9044
9044
  async function runMemoryIndex(input) {
9045
9045
  const scan = await scanVault(input.vault);
9046
9046
  if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
9047
- const warnings = [];
9048
- const sourcePages = dedupePages(scan.data.allMarkdown);
9049
- const sources = [];
9050
- for (const page of sourcePages) {
9051
- const source = await readMemorySource(page, input.project, warnings);
9052
- if (source) sources.push(source);
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
+ }
9053
9064
  }
9054
9065
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
9055
- const topics = buildTopics(input.project, sources);
9056
9066
  const relCachePath = memoryCacheRelPath(input.project);
9057
9067
  const absCachePath = join39(input.vault, relCachePath);
9058
9068
  await mkdir14(join39(input.vault, ".skillwiki", "memory", input.project), { recursive: true });
9059
9069
  await writeFile15(absCachePath, `${JSON.stringify({
9060
9070
  generated_at: generatedAt,
9061
9071
  project: input.project,
9062
- topics,
9063
- sources,
9064
- warnings
9072
+ topics: state.topics,
9073
+ sources: state.sources,
9074
+ warnings: state.warnings
9065
9075
  }, null, 2)}
9066
9076
  `, "utf8");
9067
9077
  return {
@@ -9069,18 +9079,31 @@ async function runMemoryIndex(input) {
9069
9079
  result: ok({
9070
9080
  project: input.project,
9071
9081
  generated_at: generatedAt,
9072
- topic_count: topics.length,
9073
- source_count: sources.length,
9074
- topics,
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,
9075
9088
  files_written: [relCachePath],
9076
- warnings,
9077
- 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}`
9078
9091
  })
9079
9092
  };
9080
9093
  }
9081
9094
  async function runMemoryRecall(input) {
9082
9095
  const scan = await scanVault(input.vault);
9083
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
+ }
9084
9107
  const cacheText = await readMemoryCache(input.vault, input.project);
9085
9108
  if (!cacheText) {
9086
9109
  return {
@@ -9088,6 +9111,7 @@ async function runMemoryRecall(input) {
9088
9111
  result: ok({
9089
9112
  project: input.project,
9090
9113
  topic: input.topic,
9114
+ ...scope ? { scope } : {},
9091
9115
  sources: [],
9092
9116
  humanHint: `no memory topics cache found for project ${input.project}`
9093
9117
  })
@@ -9106,12 +9130,13 @@ async function runMemoryRecall(input) {
9106
9130
  };
9107
9131
  }
9108
9132
  const limit = normalizeLimit(input.limit);
9109
- const sources = normalizeSources(parsed.sources).filter((source) => source.topics.includes(input.topic)).filter((source) => source.memory_privacy !== "secret-blocked").sort(compareSources).slice(0, limit);
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);
9110
9134
  return {
9111
9135
  exitCode: ExitCode.OK,
9112
9136
  result: ok({
9113
9137
  project: input.project,
9114
9138
  topic: input.topic,
9139
+ ...scope ? { scope } : {},
9115
9140
  sources,
9116
9141
  humanHint: renderRecallHint(input.topic, sources)
9117
9142
  })
@@ -9172,6 +9197,115 @@ async function runMemoryImport(input) {
9172
9197
  })
9173
9198
  };
9174
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
+ }
9175
9309
  async function readIfExists3(path) {
9176
9310
  try {
9177
9311
  return await readFile26(path, "utf8");
@@ -9401,6 +9535,26 @@ function compareTopics(a, b) {
9401
9535
  function compareSources(a, b) {
9402
9536
  return b.updated.localeCompare(a.updated) || a.path.localeCompare(b.path);
9403
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
+ }
9404
9558
  function renderHumanHint(topics) {
9405
9559
  if (topics.length === 0) return "no memory topics found";
9406
9560
  return topics.map((topic) => {
@@ -11231,7 +11385,7 @@ async function postCommit(vault, exitCode) {
11231
11385
  if (!porcelain || porcelain.trim().length === 0) return;
11232
11386
  const { gitStrict: gitStrict2 } = await import("./git-M4WGJ5G3.js");
11233
11387
  try {
11234
- gitStrict2(vault, ["add", "-A", "--", ...VAULT_COMMIT_PATHSPEC]);
11388
+ gitStrict2(vault, ["add", "-A", "--", "."]);
11235
11389
  for (const generatedPath of VAULT_GENERATED_COMMIT_PATHS) {
11236
11390
  try {
11237
11391
  gitStrict2(vault, ["reset", "HEAD", "--", generatedPath]);
@@ -11656,21 +11810,24 @@ memoryCmd.command("topics [vault]").description("list topic-oriented memory from
11656
11810
  limit: opts.limit
11657
11811
  }), v.vault, { postCommit: false });
11658
11812
  });
11659
- 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) => {
11660
11814
  const v = await resolveVaultArg(vault, opts.wiki);
11661
11815
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
11662
11816
  else emit(await runMemoryIndex({
11663
11817
  vault: v.vault,
11664
- project: opts.project
11665
- }), v.vault);
11818
+ project: opts.project,
11819
+ check: !!opts.check,
11820
+ ifStale: !!opts.ifStale
11821
+ }), v.vault, { postCommit: !opts.check });
11666
11822
  });
11667
- 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) => {
11668
11824
  const v = await resolveVaultArg(vault, opts.wiki);
11669
11825
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
11670
11826
  else emit(await runMemoryRecall({
11671
11827
  vault: v.vault,
11672
11828
  project: opts.project,
11673
11829
  topic: opts.topic,
11830
+ scope: opts.scope,
11674
11831
  limit: opts.limit
11675
11832
  }), v.vault, { postCommit: false });
11676
11833
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.9.20",
3
+ "version": "0.9.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.9.20",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.9.20",
3
+ "version": "0.9.21",
4
4
  "description": "Project-aware Karpathy-style knowledge base for Codex with 18 prompt-only skills backed by the deterministic skillwiki CLI.",
5
5
  "author": {
6
6
  "name": "karlorz",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.9.20",
3
+ "version": "0.9.21",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",