akm-cli 0.5.0-rc3 → 0.5.0-rc4

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
@@ -320,6 +320,12 @@ function formatPlain(command, result, detail) {
320
320
  case "index": {
321
321
  const indexResult = result;
322
322
  let out = `Indexed ${indexResult.totalEntries ?? 0} entries from ${indexResult.directoriesScanned ?? 0} directories (mode: ${indexResult.mode ?? "unknown"})`;
323
+ const warnings = indexResult.warnings;
324
+ if (Array.isArray(warnings) && warnings.length > 0) {
325
+ out += `\nWarnings (${warnings.length}):`;
326
+ for (const message of warnings)
327
+ out += `\n - ${String(message)}`;
328
+ }
323
329
  const verification = indexResult.verification;
324
330
  if (verification?.ok === false && verification.message) {
325
331
  out += `\nVerification: ${String(verification.message)}`;
@@ -458,6 +464,8 @@ function formatPlain(command, result, detail) {
458
464
  const ver = typeof src.version === "string" ? ` v${src.version}` : "";
459
465
  const prov = typeof src.provider === "string" ? ` (${src.provider})` : "";
460
466
  const flags = [];
467
+ if (typeof src.wiki === "string")
468
+ flags.push(`wiki:${src.wiki}`);
461
469
  if (src.updatable === true)
462
470
  flags.push("updatable");
463
471
  if (src.writable === true)
@@ -472,6 +480,12 @@ function formatPlain(command, result, detail) {
472
480
  const scanned = index?.directoriesScanned ?? 0;
473
481
  const total = index?.totalEntries ?? 0;
474
482
  const lines = [`Installed ${r.ref} (${scanned} directories scanned, ${total} total assets indexed)`];
483
+ const warnings = index?.warnings;
484
+ if (Array.isArray(warnings) && warnings.length > 0) {
485
+ lines.push(`Warnings (${warnings.length}):`);
486
+ for (const message of warnings)
487
+ lines.push(` - ${String(message)}`);
488
+ }
475
489
  const installed = r.installed;
476
490
  const audit = installed?.audit;
477
491
  if (audit && typeof audit === "object") {
@@ -2483,7 +2497,7 @@ const wikiCreateCommand = defineCommand({
2483
2497
  const wikiRegisterCommand = defineCommand({
2484
2498
  meta: {
2485
2499
  name: "register",
2486
- description: "Register an existing directory or repo as a first-class wiki without copying or mutating it",
2500
+ description: "Register an existing directory or repo as a first-class wiki without copying or mutating it; refreshes source and wiki search state immediately",
2487
2501
  },
2488
2502
  args: {
2489
2503
  name: { type: "positional", description: "Wiki name (lowercase, digits, hyphens)", required: true },
@@ -2543,7 +2557,7 @@ const wikiShowCommand = defineCommand({
2543
2557
  const wikiRemoveCommand = defineCommand({
2544
2558
  meta: {
2545
2559
  name: "remove",
2546
- description: "Remove a wiki. Preserves raw/ by default; pass --with-sources to also delete raw/",
2560
+ description: "Remove a wiki and refresh the index. Preserves raw/ by default; pass --with-sources to also delete raw/",
2547
2561
  },
2548
2562
  args: {
2549
2563
  name: { type: "positional", description: "Wiki name", required: true },
@@ -2565,8 +2579,10 @@ const wikiRemoveCommand = defineCommand({
2565
2579
  }
2566
2580
  const withSources = Boolean(args["with-sources"]);
2567
2581
  const { removeWiki } = await import("./wiki.js");
2582
+ const { akmIndex } = await import("./indexer");
2568
2583
  const stashDir = resolveStashDir();
2569
2584
  const result = removeWiki(stashDir, args.name, { withSources });
2585
+ await akmIndex({ stashDir });
2570
2586
  output("wiki-remove", result);
2571
2587
  });
2572
2588
  },
@@ -2591,7 +2607,7 @@ const wikiPagesCommand = defineCommand({
2591
2607
  const wikiSearchCommand = defineCommand({
2592
2608
  meta: {
2593
2609
  name: "search",
2594
- description: "Search wiki pages within a single wiki (scoped wrapper over `akm search --type wiki`; excludes raw/schema/index/log)",
2610
+ description: "Search wiki pages within a single wiki (scoped wrapper over `akm search --type wiki`; excludes raw/schema/index/log and returns canonical wiki refs)",
2595
2611
  },
2596
2612
  args: {
2597
2613
  name: { type: "positional", description: "Wiki name", required: true },
@@ -2744,7 +2760,18 @@ const main = defineCommand({
2744
2760
  });
2745
2761
  const CONFIG_SUBCOMMAND_SET = new Set(["path", "list", "get", "set", "unset"]);
2746
2762
  const VAULT_SUBCOMMAND_SET = new Set(["list", "show", "create", "set", "unset", "load"]);
2747
- const WIKI_SUBCOMMAND_SET = new Set(["create", "list", "show", "remove", "pages", "search", "stash", "lint", "ingest"]);
2763
+ const WIKI_SUBCOMMAND_SET = new Set([
2764
+ "create",
2765
+ "register",
2766
+ "list",
2767
+ "show",
2768
+ "remove",
2769
+ "pages",
2770
+ "search",
2771
+ "stash",
2772
+ "lint",
2773
+ "ingest",
2774
+ ]);
2748
2775
  const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
2749
2776
  // citty reads process.argv directly and does not accept a custom argv array,
2750
2777
  // so we must replace process.argv with the normalized version before runMain.
package/dist/indexer.js CHANGED
@@ -80,7 +80,7 @@ export async function akmIndex(options) {
80
80
  // doFullDelete=true merges the wipe into the same transaction as the
81
81
  // inserts so readers never see an empty database mid-rebuild.
82
82
  const doFullDelete = options?.full || !isIncremental;
83
- const { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm } = await indexEntries(db, allStashSources, isIncremental, builtAtMs, doFullDelete);
83
+ const { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm, warnings } = await indexEntries(db, allStashSources, isIncremental, builtAtMs, doFullDelete);
84
84
  onProgress({
85
85
  phase: "scan",
86
86
  message: `Scanned ${scannedDirs} ${scannedDirs === 1 ? "directory" : "directories"} and skipped ${skippedDirs}.`,
@@ -166,6 +166,7 @@ export async function akmIndex(options) {
166
166
  mode: isIncremental ? "incremental" : "full",
167
167
  directoriesScanned: scannedDirs,
168
168
  directoriesSkipped: skippedDirs,
169
+ ...(warnings.length > 0 ? { warnings } : {}),
169
170
  verification,
170
171
  timing: {
171
172
  totalMs: tEnd - t0,
@@ -185,6 +186,7 @@ async function indexEntries(db, allStashSources, isIncremental, builtAtMs, doFul
185
186
  let scannedDirs = 0;
186
187
  let skippedDirs = 0;
187
188
  let generatedCount = 0;
189
+ const warnings = [];
188
190
  const seenPaths = new Set();
189
191
  const dirsNeedingLlm = [];
190
192
  const dirRecords = [];
@@ -261,6 +263,8 @@ async function indexEntries(db, allStashSources, isIncremental, builtAtMs, doFul
261
263
  const uncoveredFiles = files.filter((f) => !coveredFiles.has(path.basename(f)));
262
264
  if (uncoveredFiles.length > 0) {
263
265
  const generated = await generateMetadataFlat(currentStashDir, uncoveredFiles);
266
+ if (generated.warnings?.length)
267
+ warnings.push(...generated.warnings);
264
268
  if (generated.entries.length > 0) {
265
269
  stash = { entries: [...stash.entries, ...generated.entries] };
266
270
  generatedCount += generated.entries.length;
@@ -269,6 +273,8 @@ async function indexEntries(db, allStashSources, isIncremental, builtAtMs, doFul
269
273
  }
270
274
  if (!stash) {
271
275
  const generated = await generateMetadataFlat(currentStashDir, files);
276
+ if (generated.warnings?.length)
277
+ warnings.push(...generated.warnings);
272
278
  if (generated.entries.length > 0) {
273
279
  stash = { entries: generated.entries };
274
280
  generatedCount += generated.entries.length;
@@ -351,7 +357,7 @@ async function indexEntries(db, allStashSources, isIncremental, builtAtMs, doFul
351
357
  }
352
358
  });
353
359
  insertTransaction();
354
- return { scannedDirs, skippedDirs, generatedCount, dirsNeedingLlm };
360
+ return { scannedDirs, skippedDirs, generatedCount, warnings, dirsNeedingLlm };
355
361
  }
356
362
  async function enhanceDirsWithLlm(db, config, dirsNeedingLlm) {
357
363
  if (!config.llm || dirsNeedingLlm.length === 0)
@@ -29,6 +29,7 @@ export async function akmListSources(input) {
29
29
  sources.push({
30
30
  name,
31
31
  kind,
32
+ wiki: stash.wikiName,
32
33
  path: stash.path,
33
34
  provider: isRemote ? stash.type : undefined,
34
35
  updatable: false,
@@ -44,6 +45,7 @@ export async function akmListSources(input) {
44
45
  sources.push({
45
46
  name: entry.id,
46
47
  kind,
48
+ wiki: entry.wikiName,
47
49
  path: entry.stashRoot,
48
50
  ref: entry.ref,
49
51
  version: entry.resolvedVersion,
@@ -29,6 +29,15 @@ export function buildLocalAction(type, ref) {
29
29
  const builder = ACTION_BUILDERS[type];
30
30
  return builder ? builder(ref) : `akm show ${ref}`;
31
31
  }
32
+ function resolveSearchHitRef(entry, refName, source) {
33
+ if (source?.wikiName) {
34
+ return makeAssetRef(entry.type, entry.name);
35
+ }
36
+ return makeAssetRef(entry.type, refName, source?.registryId);
37
+ }
38
+ function resolveSearchHitOrigin(source) {
39
+ return source?.wikiName ? null : (source?.registryId ?? null);
40
+ }
32
41
  // ── Main search entrypoint ───────────────────────────────────────────────────
33
42
  export async function searchLocal(input) {
34
43
  const { query, searchType, limit, stashDir, sources, config } = input;
@@ -448,20 +457,21 @@ export async function buildDbHit(input) {
448
457
  const score = Math.round(input.score * 10000) / 10000;
449
458
  const whyMatched = buildWhyMatched(input.entry, input.query, input.rankingMode, qualityBoost, confidenceBoost, input.utilityBoosted);
450
459
  const source = findSourceForPath(input.path, input.sources);
460
+ const ref = resolveSearchHitRef(input.entry, refName, source);
451
461
  const editable = isEditable(input.path, input.config);
452
462
  const estimatedTokens = typeof input.entry.fileSize === "number" ? Math.round(input.entry.fileSize / 4) : undefined;
453
463
  const hit = {
454
464
  type: input.entry.type,
455
465
  name: input.entry.name,
456
466
  path: input.path,
457
- ref: makeAssetRef(input.entry.type, refName, source?.registryId),
458
- origin: source?.registryId ?? null,
467
+ ref,
468
+ origin: resolveSearchHitOrigin(source),
459
469
  editable,
460
470
  ...(!editable ? { editHint: buildEditHint(input.path, input.entry.type, refName, source?.registryId) } : {}),
461
471
  description: input.entry.description,
462
472
  tags: input.entry.tags,
463
473
  size: deriveSize(input.entry.fileSize),
464
- action: buildLocalAction(input.entry.type, makeAssetRef(input.entry.type, refName, source?.registryId)),
474
+ action: buildLocalAction(input.entry.type, ref),
465
475
  score,
466
476
  whyMatched,
467
477
  ...(estimatedTokens !== undefined ? { estimatedTokens } : {}),
@@ -523,7 +533,7 @@ rankingMode, qualityBoost, confidenceBoost, utilityBoosted) {
523
533
  async function assetToSearchHit(asset, stashDir, sources, config, score) {
524
534
  const source = findSourceForPath(asset.path, sources);
525
535
  const editable = isEditable(asset.path, config);
526
- const ref = makeAssetRef(asset.entry.type, asset.entry.name, source?.registryId);
536
+ const ref = resolveSearchHitRef(asset.entry, asset.entry.name, source);
527
537
  const fileSize = readFileSize(asset.path);
528
538
  const size = deriveSize(fileSize);
529
539
  const estimatedTokens = typeof fileSize === "number" ? Math.round(fileSize / 4) : undefined;
@@ -532,7 +542,7 @@ async function assetToSearchHit(asset, stashDir, sources, config, score) {
532
542
  name: asset.entry.name,
533
543
  path: asset.path,
534
544
  ref,
535
- origin: source?.registryId ?? null,
545
+ origin: resolveSearchHitOrigin(source),
536
546
  editable,
537
547
  ...(!editable
538
548
  ? { editHint: buildEditHint(asset.path, asset.entry.type, asset.entry.name, source?.registryId) }
package/dist/metadata.js CHANGED
@@ -362,6 +362,7 @@ function mergeParameters(existing, additional) {
362
362
  // ── Metadata Generation ─────────────────────────────────────────────────────
363
363
  export async function generateMetadata(dirPath, assetType, files, typeRoot = dirPath) {
364
364
  const entries = [];
365
+ const warnings = [];
365
366
  const pkgMeta = extractPackageMetadata(dirPath);
366
367
  for (const file of files) {
367
368
  const ext = path.extname(file).toLowerCase();
@@ -428,7 +429,13 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
428
429
  const renderer = await getRenderer(match.renderer);
429
430
  if (renderer?.extractMetadata) {
430
431
  const renderCtx = buildRenderContext(fileCtx, match, [typeRoot]);
431
- renderer.extractMetadata(entry, renderCtx);
432
+ try {
433
+ renderer.extractMetadata(entry, renderCtx);
434
+ }
435
+ catch (error) {
436
+ warnings.push(buildMetadataSkipWarning(file, assetType, error));
437
+ continue;
438
+ }
432
439
  }
433
440
  }
434
441
  // Priority 4: Filename heuristics (fallback)
@@ -447,7 +454,7 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
447
454
  entry.filename = path.basename(file);
448
455
  entries.push(entry);
449
456
  }
450
- return { entries };
457
+ return warnings.length > 0 ? { entries, warnings } : { entries };
451
458
  }
452
459
  /**
453
460
  * Generate metadata for files using the matcher system instead of a fixed asset type.
@@ -458,6 +465,7 @@ export async function generateMetadata(dirPath, assetType, files, typeRoot = dir
458
465
  */
459
466
  export async function generateMetadataFlat(stashRoot, files) {
460
467
  const entries = [];
468
+ const warnings = [];
461
469
  const pkgMetaCache = new Map();
462
470
  for (const file of files) {
463
471
  if (!shouldIndexStashFile(stashRoot, file))
@@ -534,7 +542,13 @@ export async function generateMetadataFlat(stashRoot, files) {
534
542
  const renderer = await getRenderer(match.renderer);
535
543
  if (renderer?.extractMetadata) {
536
544
  const renderCtx = buildRenderContext(ctx, match, [stashRoot]);
537
- renderer.extractMetadata(entry, renderCtx);
545
+ try {
546
+ renderer.extractMetadata(entry, renderCtx);
547
+ }
548
+ catch (error) {
549
+ warnings.push(buildMetadataSkipWarning(file, assetType, error));
550
+ continue;
551
+ }
538
552
  }
539
553
  // Filename heuristics fallback
540
554
  if (!entry.description) {
@@ -550,7 +564,13 @@ export async function generateMetadataFlat(stashRoot, files) {
550
564
  entry.filename = path.basename(file);
551
565
  entries.push(entry);
552
566
  }
553
- return { entries };
567
+ return warnings.length > 0 ? { entries, warnings } : { entries };
568
+ }
569
+ function buildMetadataSkipWarning(filePath, assetType, error) {
570
+ const detail = error instanceof Error ? error.message : String(error);
571
+ const warning = `Skipped malformed ${assetType} asset at ${filePath}: ${detail}`;
572
+ warn(warning);
573
+ return warning;
554
574
  }
555
575
  function normalizeTerms(values) {
556
576
  const normalized = new Set();
package/dist/stash-add.js CHANGED
@@ -72,31 +72,36 @@ async function addLocalStashSource(ref, sourcePath, stashDir, wikiName) {
72
72
  // Check for duplicates in stashes[]
73
73
  const stashes = [...(config.stashes ?? [])];
74
74
  const existing = stashes.find((s) => s.type === "filesystem" && s.path && path.resolve(s.path) === resolvedPath);
75
+ let persistedEntry;
75
76
  if (!existing) {
76
- const entry = {
77
+ persistedEntry = {
77
78
  type: "filesystem",
78
79
  path: resolvedPath,
79
80
  name: wikiName ?? toReadableId(resolvedPath),
80
81
  ...(wikiName ? { wikiName } : {}),
81
82
  };
82
- stashes.push(entry);
83
+ stashes.push(persistedEntry);
83
84
  saveConfig({ ...config, stashes });
84
85
  }
85
- else if (wikiName && existing.wikiName !== wikiName) {
86
- existing.wikiName = wikiName;
87
- saveConfig({ ...config, stashes });
86
+ else {
87
+ if (wikiName && existing.wikiName !== wikiName) {
88
+ existing.wikiName = wikiName;
89
+ saveConfig({ ...config, stashes });
90
+ }
91
+ persistedEntry = existing;
88
92
  }
89
93
  const index = await akmIndex({ stashDir });
90
94
  const updatedConfig = loadConfig();
91
95
  return {
92
96
  schemaVersion: 1,
93
97
  stashDir,
94
- ref,
98
+ ref: wikiName ?? ref,
95
99
  stashSource: {
96
100
  type: "filesystem",
97
101
  path: resolvedPath,
98
- name: toReadableId(resolvedPath),
102
+ name: persistedEntry.name ?? toReadableId(resolvedPath),
99
103
  stashRoot: resolvedPath,
104
+ ...(persistedEntry.wikiName ? { wiki: persistedEntry.wikiName } : {}),
100
105
  },
101
106
  config: {
102
107
  stashCount: updatedConfig.stashes?.length ?? 0,
@@ -107,6 +112,7 @@ async function addLocalStashSource(ref, sourcePath, stashDir, wikiName) {
107
112
  totalEntries: index.totalEntries,
108
113
  directoriesScanned: index.directoriesScanned,
109
114
  directoriesSkipped: index.directoriesSkipped,
115
+ ...(index.warnings?.length ? { warnings: index.warnings } : {}),
110
116
  },
111
117
  };
112
118
  }
@@ -145,12 +151,13 @@ async function addWebsiteStashSource(ref, stashDir, name, options, wikiName) {
145
151
  return {
146
152
  schemaVersion: 1,
147
153
  stashDir,
148
- ref,
154
+ ref: wikiName ?? ref,
149
155
  stashSource: {
150
156
  type: "website",
151
157
  url: normalizedUrl,
152
158
  name: entry.name,
153
159
  stashRoot: cachePaths.stashDir,
160
+ ...(entry.wikiName ? { wiki: entry.wikiName } : {}),
154
161
  },
155
162
  config: {
156
163
  stashCount: updatedConfig.stashes?.length ?? 0,
@@ -161,6 +168,7 @@ async function addWebsiteStashSource(ref, stashDir, name, options, wikiName) {
161
168
  totalEntries: index.totalEntries,
162
169
  directoriesScanned: index.directoriesScanned,
163
170
  directoriesSkipped: index.directoriesSkipped,
171
+ ...(index.warnings?.length ? { warnings: index.warnings } : {}),
164
172
  },
165
173
  };
166
174
  }
@@ -227,6 +235,7 @@ async function addRegistryKit(ref, stashDir, trustThisInstall, writable, wikiNam
227
235
  totalEntries: index.totalEntries,
228
236
  directoriesScanned: index.directoriesScanned,
229
237
  directoriesSkipped: index.directoriesSkipped,
238
+ ...(index.warnings?.length ? { warnings: index.warnings } : {}),
230
239
  },
231
240
  };
232
241
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.5.0-rc3",
3
+ "version": "0.5.0-rc4",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Kit Manager) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
6
6
  "keywords": [