agdex 0.7.0 → 0.8.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agdex
2
2
 
3
- Embed compressed documentation indexes into `AGENTS.md` or `CLAUDE.md` for AI coding agents.
3
+ Embed compressed documentation indexes into local agent instruction files.
4
4
 
5
5
  This package helps AI coding agents (Claude, Cursor, etc.) work with version-matched framework documentation by embedding a compressed docs index directly into your project's markdown file. Based on [Vercel's research](https://vercel.com/blog/teaching-ai-agents-how-to-use-nextjs) showing that embedded docs achieve 100% pass rates compared to 79% for skills.
6
6
 
@@ -10,8 +10,9 @@ AI coding agents rely on training data that becomes outdated. When agents don't
10
10
 
11
11
  1. Downloads version-matched documentation from GitHub
12
12
  2. Creates a compressed index (~8KB for Next.js)
13
- 3. Embeds it in your `AGENTS.md` or `CLAUDE.md`
14
- 4. Agents can then retrieve specific docs on demand
13
+ 3. Stores the docs in local `.agdex/` cache
14
+ 4. Embeds an index in your local agent instruction file
15
+ 5. Agents can then retrieve specific docs on demand
15
16
 
16
17
  The key instruction embedded tells agents to **prefer retrieval-led reasoning over pre-training-led reasoning**.
17
18
 
@@ -38,7 +39,7 @@ Create a `.agdexrc.json` file in your project root:
38
39
 
39
40
  ```json
40
41
  {
41
- "output": "CLAUDE.md"
42
+ "output": "CLAUDE.local.md"
42
43
  }
43
44
  ```
44
45
 
@@ -50,7 +51,7 @@ Add an `agdex` field to your `package.json`:
50
51
  {
51
52
  "name": "my-project",
52
53
  "agdex": {
53
- "output": "CLAUDE.md"
54
+ "output": "CLAUDE.local.md"
54
55
  }
55
56
  }
56
57
  ```
@@ -61,7 +62,7 @@ Add an `agdex` field to your `package.json`:
61
62
 
62
63
  | Option | Type | Default | Description |
63
64
  |----------|--------|-------------|-------------|
64
- | `output` | string | `CLAUDE.md` | Default output file for indexes |
65
+ | `output` | string | `CLAUDE.local.md` | Default output file for indexes |
65
66
 
66
67
  ## CLI Usage
67
68
 
@@ -112,10 +113,10 @@ npx agdex --provider nextjs --description "Project uses App Router only"
112
113
  ```bash
113
114
  -p, --provider <name> Documentation provider (nextjs, react, etc.)
114
115
  --fw-version <version> Framework version (auto-detected if not provided)
115
- -o, --output <file> Target file (default: from config or CLAUDE.md)
116
+ -o, --output <file> Target file (default: from config or CLAUDE.local.md)
116
117
  -d, --description <text> Additional description to include in the index
117
- -g, --global Use global cache ~/.cache/agdex/ (default)
118
- -l, --local Use local .agdex/ instead
118
+ -g, --global Use global cache ~/.cache/agdex/ instead of local .agdex/
119
+ -l, --local Use local .agdex/ (default)
119
120
  ```
120
121
 
121
122
  ### Custom GitHub Repository
@@ -209,6 +210,35 @@ npx agdex remove --docs
209
210
  npx agdex remove --skills
210
211
  ```
211
212
 
213
+ ### Maintaining Indexes
214
+
215
+ Successful embeds write local maintenance state to `.agdex/agdex.lock`. The lockfile records the target agent instruction file, marker, source metadata, cache path, and display command for each last-known-good index. It is generated local state and should stay unversioned with `.agdex/`.
216
+
217
+ Inspect index health:
218
+
219
+ ```bash
220
+ npx agdex status
221
+ npx agdex status --check
222
+ npx agdex status --json
223
+ npx agdex status --output AGENTS.md
224
+ ```
225
+
226
+ Refresh lockfile-backed indexes:
227
+
228
+ ```bash
229
+ npx agdex refresh
230
+ npx agdex refresh --force
231
+ npx agdex refresh --repair
232
+ npx agdex refresh --provider nextjs
233
+ ```
234
+
235
+ Create lockfile entries for existing embedded docs markers when agdex can safely infer marker and cache metadata:
236
+
237
+ ```bash
238
+ npx agdex migrate
239
+ npx agdex migrate --output AGENTS.md
240
+ ```
241
+
212
242
  ### List Available Providers
213
243
 
214
244
  ```bash
@@ -224,7 +254,7 @@ import { embed, nextjsProvider, createProvider } from 'agdex'
224
254
  const result = await embed({
225
255
  cwd: process.cwd(),
226
256
  provider: nextjsProvider,
227
- output: 'AGENTS.md'
257
+ output: 'CLAUDE.local.md'
228
258
  })
229
259
 
230
260
  // Create custom provider
@@ -240,7 +270,7 @@ await embed({
240
270
  cwd: process.cwd(),
241
271
  provider: myProvider,
242
272
  version: '1.0.0',
243
- output: 'CLAUDE.md'
273
+ output: 'CLAUDE.local.md'
244
274
  })
245
275
  ```
246
276
 
package/dist/cli/index.js CHANGED
@@ -6,11 +6,14 @@ import {
6
6
  collectAllSkills,
7
7
  collectDocFiles,
8
8
  convexProvider,
9
+ createIndexId,
9
10
  createProvider,
11
+ createStatusReport,
10
12
  deltaRsProvider,
11
13
  discoverSkillsShRepo,
12
14
  embed,
13
15
  embedSkills,
16
+ ensureGitignoreEntry,
14
17
  fetchSkillsShSearch,
15
18
  ffmpegProvider,
16
19
  generateIndex,
@@ -32,19 +35,21 @@ import {
32
35
  pixiProvider,
33
36
  polarsProvider,
34
37
  rattlerBuildProvider,
38
+ readIndexLockfile,
35
39
  removeDocsIndex,
36
40
  removeSkillsIndex,
37
41
  ruffProvider,
38
42
  svelteProvider,
39
43
  tailwindProvider,
40
44
  tauriProvider,
41
- tyProvider
42
- } from "../index-ecjc59t4.js";
45
+ tyProvider,
46
+ upsertIndexLockEntry
47
+ } from "../index-9k6cxm1y.js";
43
48
  import {
44
49
  __commonJS,
45
50
  __require,
46
51
  __toESM
47
- } from "../index-dtcewfnz.js";
52
+ } from "../index-pyanjjwn.js";
48
53
 
49
54
  // node_modules/commander/lib/error.js
50
55
  var require_error = __commonJS((exports) => {
@@ -2109,7 +2114,7 @@ var require_clear = __commonJS((exports, module) => {
2109
2114
  if (it)
2110
2115
  o = it;
2111
2116
  var i = 0;
2112
- var F = function F2() {};
2117
+ var F = function F() {};
2113
2118
  return { s: F, n: function n() {
2114
2119
  if (i >= o.length)
2115
2120
  return { done: true };
@@ -4441,7 +4446,7 @@ var require_dist = __commonJS((exports, module) => {
4441
4446
  if (it)
4442
4447
  o = it;
4443
4448
  var i = 0;
4444
- var F = function F2() {};
4449
+ var F = function F() {};
4445
4450
  return { s: F, n: function n() {
4446
4451
  if (i >= o.length)
4447
4452
  return { done: true };
@@ -4544,7 +4549,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
4544
4549
  }
4545
4550
  return question2.format ? yield question2.format(answer2, answers) : answer2;
4546
4551
  });
4547
- return function getFormattedAnswer2(_x, _x2) {
4552
+ return function getFormattedAnswer(_x, _x2) {
4548
4553
  return _ref.apply(this, arguments);
4549
4554
  };
4550
4555
  }();
@@ -7048,12 +7053,21 @@ function onCancel() {
7048
7053
  Cancelled.`));
7049
7054
  process.exit(0);
7050
7055
  }
7056
+ function resolveGlobalCacheOption(options) {
7057
+ if (options.global && options.local) {
7058
+ console.error(import_picocolors2.default.red("Choose either --global or --local, not both."));
7059
+ process.exit(1);
7060
+ }
7061
+ return options.global ? true : false;
7062
+ }
7051
7063
  async function runEmbed(options) {
7052
7064
  const cwd = process.cwd();
7065
+ const globalCache = resolveGlobalCacheOption(options);
7053
7066
  if (options.url) {
7054
7067
  await runUrl(options.url, {
7055
7068
  output: options.output,
7056
- name: options.description
7069
+ name: options.description,
7070
+ global: globalCache
7057
7071
  });
7058
7072
  return;
7059
7073
  }
@@ -7085,8 +7099,7 @@ async function runEmbed(options) {
7085
7099
  provider = result.provider;
7086
7100
  version = result.version;
7087
7101
  output = result.output;
7088
- const useGlobal = options.local ? false : undefined;
7089
- await executeEmbed(cwd, provider, version, output, useGlobal, result.description);
7102
+ await executeEmbed(cwd, provider, version, output, globalCache, result.description, version ? "auto" : "unknown");
7090
7103
  return;
7091
7104
  }
7092
7105
  } else {
@@ -7094,8 +7107,7 @@ async function runEmbed(options) {
7094
7107
  provider = result.provider;
7095
7108
  version = result.version;
7096
7109
  output = result.output;
7097
- const useGlobal = options.local ? false : undefined;
7098
- await executeEmbed(cwd, provider, version, output, useGlobal, result.description);
7110
+ await executeEmbed(cwd, provider, version, output, globalCache, result.description, version ? "auto" : "unknown");
7099
7111
  return;
7100
7112
  }
7101
7113
  output = options.output || getDefaultOutput();
@@ -7103,10 +7115,9 @@ async function runEmbed(options) {
7103
7115
  console.error(import_picocolors2.default.red(`Provider ${provider.displayName} requires --version flag since auto-detection is not supported.`));
7104
7116
  process.exit(1);
7105
7117
  }
7106
- const useGlobalCache = options.local ? false : undefined;
7107
- await executeEmbed(cwd, provider, version, output, useGlobalCache, options.description);
7118
+ await executeEmbed(cwd, provider, version, output, globalCache, options.description, options.fwVersion ? "pinned" : version ? "auto" : "unknown");
7108
7119
  }
7109
- async function executeEmbed(cwd, provider, version, output, globalCache, description) {
7120
+ async function executeEmbed(cwd, provider, version, output, globalCache, description, versionMode) {
7110
7121
  let resolvedVersion = version;
7111
7122
  let usingDefaultBranch = false;
7112
7123
  if (!resolvedVersion && provider.urlConfig) {
@@ -7122,8 +7133,10 @@ async function executeEmbed(cwd, provider, version, output, globalCache, descrip
7122
7133
  `));
7123
7134
  resolvedVersion = fallbackBranch;
7124
7135
  usingDefaultBranch = true;
7136
+ versionMode = versionMode || "default-branch";
7125
7137
  } else {
7126
7138
  resolvedVersion = detected.version;
7139
+ versionMode = versionMode || "auto";
7127
7140
  }
7128
7141
  }
7129
7142
  const versionLabel = usingDefaultBranch ? "latest" : resolvedVersion;
@@ -7133,6 +7146,7 @@ Embedding ${import_picocolors2.default.cyan(provider.displayName)} ${import_pico
7133
7146
  cwd,
7134
7147
  provider,
7135
7148
  version: resolvedVersion,
7149
+ versionMode,
7136
7150
  output,
7137
7151
  globalCache,
7138
7152
  description
@@ -7347,7 +7361,7 @@ No providers selected.
7347
7361
  const output = await promptForOutputFile();
7348
7362
  for (const item of selected) {
7349
7363
  const provider = getProvider(item.value);
7350
- await executeEmbed(cwd, provider, item.version, output, undefined, item.description || undefined);
7364
+ await executeEmbed(cwd, provider, item.version, output, undefined, item.description || undefined, item.version ? "pinned" : "auto");
7351
7365
  }
7352
7366
  process.exit(0);
7353
7367
  }
@@ -7564,6 +7578,21 @@ Building index from ${import_picocolors2.default.cyan(docsPath)}...`);
7564
7578
  const newContent = injectIndex(existingContent, indexContent, providerName);
7565
7579
  fs.writeFileSync(targetPath, newContent, "utf-8");
7566
7580
  const sizeAfter = Buffer.byteLength(newContent, "utf-8");
7581
+ upsertIndexLockEntry(cwd, {
7582
+ id: createIndexId("docs", providerName, output),
7583
+ kind: "docs",
7584
+ source: {
7585
+ type: "local-docs",
7586
+ name: providerName,
7587
+ displayName: name,
7588
+ docsPath,
7589
+ versionMode: "unknown"
7590
+ },
7591
+ targetFile: output,
7592
+ marker: providerName,
7593
+ cachePath: absoluteDocsPath,
7594
+ command: `npx agdex local ${docsPath} --name "${name}" --output ${output}`
7595
+ });
7567
7596
  const action = isNewFile ? "Created" : "Updated";
7568
7597
  const sizeInfo = isNewFile ? formatSize(sizeAfter) : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
7569
7598
  console.log(`${import_picocolors2.default.green("✓")} ${action} ${import_picocolors2.default.bold(output)} (${sizeInfo})`);
@@ -7571,13 +7600,14 @@ Building index from ${import_picocolors2.default.cyan(docsPath)}...`);
7571
7600
  }
7572
7601
  async function runUrl(url, options) {
7573
7602
  const cwd = process.cwd();
7574
- const { createUrlProvider } = await import("../url-scraper-5sj8c56t.js");
7575
- const { pullDocsFromUrl } = await import("../url-scraper-5sj8c56t.js");
7603
+ const { pullDocsFromUrl } = await import("../url-scraper-axfs3dz7.js");
7576
7604
  const name = options.name || new URL(url).hostname.replace(/^docs\./, "").replace(/\.\w+$/, "");
7577
7605
  const providerName = name.toLowerCase().replace(/\s+/g, "-");
7578
7606
  const output = options.output || getDefaultOutput();
7579
- const cacheBase = path.join(os.homedir(), ".cache", "agdex");
7580
- const docsPath = path.join(cacheBase, providerName);
7607
+ const globalCache = resolveGlobalCacheOption(options);
7608
+ const docsDir = globalCache ? path.join(os.homedir(), ".cache", "agdex", providerName) : path.join(".agdex", providerName);
7609
+ const docsPath = path.isAbsolute(docsDir) ? docsDir : path.join(cwd, docsDir);
7610
+ const docsLinkPath = globalCache ? docsPath : `./${docsDir}`;
7581
7611
  console.log(`
7582
7612
  Scraping documentation from ${import_picocolors2.default.cyan(url)}...`);
7583
7613
  const cacheHit = fs.existsSync(docsPath) && fs.readdirSync(docsPath).length > 0;
@@ -7616,19 +7646,41 @@ ${import_picocolors2.default.green("✓")} Downloaded docs to ${import_picocolor
7616
7646
  const docFiles = collectDocFiles(docsPath, { extensions: [".md"] });
7617
7647
  const sections = buildDocTree(docFiles);
7618
7648
  const indexContent = generateIndex({
7619
- docsPath,
7649
+ docsPath: docsLinkPath,
7620
7650
  sections,
7621
7651
  outputFile: output,
7622
7652
  providerName: name,
7623
7653
  instruction: `IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any ${name} tasks.`,
7624
- regenerateCommand: `npx agdex url "${url}" --name "${name}" --output ${output}`
7654
+ regenerateCommand: `npx agdex url "${url}" --name "${name}" --output ${output}${globalCache ? " --global" : ""}`
7625
7655
  });
7626
7656
  const newContent = injectIndex(existingContent, indexContent, providerName);
7627
7657
  fs.writeFileSync(targetPath, newContent, "utf-8");
7628
7658
  const sizeAfter = Buffer.byteLength(newContent, "utf-8");
7659
+ upsertIndexLockEntry(cwd, {
7660
+ id: createIndexId("docs", providerName, output),
7661
+ kind: "docs",
7662
+ source: {
7663
+ type: "url-docs",
7664
+ name: providerName,
7665
+ displayName: name,
7666
+ url,
7667
+ version: "latest",
7668
+ versionMode: "default-branch"
7669
+ },
7670
+ targetFile: output,
7671
+ marker: providerName,
7672
+ cachePath: docsPath,
7673
+ command: `npx agdex url "${url}" --name "${name}" --output ${output}${globalCache ? " --global" : ""}`
7674
+ });
7629
7675
  const action = isNewFile ? "Created" : "Updated";
7630
7676
  const sizeInfo = isNewFile ? formatSize(sizeAfter) : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
7631
7677
  console.log(`${import_picocolors2.default.green("✓")} ${action} ${import_picocolors2.default.bold(output)} (${sizeInfo})`);
7678
+ if (!globalCache) {
7679
+ const gitignoreResult = ensureGitignoreEntry(cwd, ".agdex");
7680
+ if (gitignoreResult.updated) {
7681
+ console.log(`${import_picocolors2.default.green("✓")} Added ${import_picocolors2.default.bold(".agdex")} to .gitignore`);
7682
+ }
7683
+ }
7632
7684
  console.log("");
7633
7685
  }
7634
7686
  function runList() {
@@ -7677,10 +7729,191 @@ Sources you can index:
7677
7729
  • Claude Code skills
7678
7730
 
7679
7731
  Run 'agdex' without arguments for interactive mode.`).version("0.4.2");
7680
- program2.command("embed", { isDefault: true }).description("Embed documentation index into AGENTS.md/CLAUDE.md").option("-p, --provider <name>", "Documentation provider (nextjs, react, etc.)").option("--fw-version <version>", "Framework version (auto-detected if not provided)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("--repo <owner/repo>", "Custom GitHub repository").option("--docs-path <path>", "Path to docs folder in repository").option("-g, --global", "Store docs in global cache (~/.cache/agdex/) (default)").option("-l, --local", "Store docs in local .agdex/ instead of global cache").option("-d, --description <text>", "Additional description to include in the index").option("-u, --url <url>", "Scrape documentation from a website URL").action(runEmbed);
7732
+ program2.command("embed", { isDefault: true }).description("Embed documentation index into AGENTS.md/CLAUDE.md").option("-p, --provider <name>", "Documentation provider (nextjs, react, etc.)").option("--fw-version <version>", "Framework version (auto-detected if not provided)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("--repo <owner/repo>", "Custom GitHub repository").option("--docs-path <path>", "Path to docs folder in repository").option("-g, --global", "Store docs in global cache (~/.cache/agdex/) instead of local .agdex/").option("-l, --local", "Store docs in local .agdex/ (default)").option("-d, --description <text>", "Additional description to include in the index").option("-u, --url <url>", "Scrape documentation from a website URL").action(runEmbed);
7681
7733
  program2.command("local <docs-path>").description("Build index from local documentation directory").option("-n, --name <name>", "Display name for the documentation").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("-e, --extensions <exts>", "File extensions to include (comma-separated, default: .md,.mdx)").action(runLocal);
7682
- program2.command("url <url>").description("Scrape documentation from a website URL and build index").option("-n, --name <name>", "Display name for the documentation (default: derived from URL)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("-s, --selector <css>", "CSS selector for main content (default: main#main-content, main, article)").option("-c, --concurrency <n>", "Max concurrent fetches (default: 5)").option("--delay <ms>", "Delay between fetch batches in ms (default: 200)").action(runUrl);
7734
+ program2.command("url <url>").description("Scrape documentation from a website URL and build index").option("-n, --name <name>", "Display name for the documentation (default: derived from URL)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("-s, --selector <css>", "CSS selector for main content (default: main#main-content, main, article)").option("-c, --concurrency <n>", "Max concurrent fetches (default: 5)").option("--delay <ms>", "Delay between fetch batches in ms (default: 200)").option("-g, --global", "Store docs in global cache (~/.cache/agdex/) instead of local .agdex/").option("-l, --local", "Store docs in local .agdex/ (default)").action(runUrl);
7683
7735
  program2.command("list").description("List available documentation providers").action(runList);
7736
+ function runStatus(options) {
7737
+ const report = createStatusReport({
7738
+ cwd: process.cwd(),
7739
+ targetFile: options.output
7740
+ });
7741
+ const unhealthy = report.indexes.filter((index) => index.health !== "ok");
7742
+ if (options.json) {
7743
+ console.log(JSON.stringify(report, null, 2));
7744
+ } else {
7745
+ printStatusReport(report.indexes, report.scannedFiles);
7746
+ }
7747
+ if (options.check && unhealthy.length > 0) {
7748
+ process.exit(1);
7749
+ }
7750
+ }
7751
+ function printStatusReport(indexes, scannedFiles) {
7752
+ console.log(import_picocolors2.default.cyan(`
7753
+ agdex status
7754
+ `));
7755
+ console.log(import_picocolors2.default.gray(` Scanned: ${scannedFiles.length > 0 ? scannedFiles.join(", ") : "no agent instruction files found"}`));
7756
+ if (indexes.length === 0) {
7757
+ console.log(import_picocolors2.default.yellow(`
7758
+ No indexes found.
7759
+ `));
7760
+ return;
7761
+ }
7762
+ console.log("");
7763
+ for (const index of indexes) {
7764
+ const symbol = index.health === "ok" ? import_picocolors2.default.green("✓") : import_picocolors2.default.yellow("!");
7765
+ const source = index.source?.displayName || index.source?.name || index.marker;
7766
+ console.log(` ${symbol} ${import_picocolors2.default.bold(index.kind)} ${source} ${import_picocolors2.default.gray(`(${index.targetFile})`)}`);
7767
+ console.log(` health: ${formatHealth(index.health)}`);
7768
+ if (index.cachePath) {
7769
+ console.log(` cache: ${index.cachePath}`);
7770
+ }
7771
+ if (index.health !== "ok") {
7772
+ console.log(` action: ${index.suggestedAction}`);
7773
+ }
7774
+ }
7775
+ console.log("");
7776
+ }
7777
+ function formatHealth(health) {
7778
+ if (health === "ok")
7779
+ return import_picocolors2.default.green(health);
7780
+ return import_picocolors2.default.yellow(health);
7781
+ }
7782
+ program2.command("status").description("Inspect lockfile-backed index health").option("-o, --output <file>", "Scope status to one agent instruction file").option("--json", "Print machine-readable JSON").option("--check", "Exit non-zero when any discovered index is unhealthy").action(runStatus);
7783
+ async function runRefresh(options) {
7784
+ const cwd = process.cwd();
7785
+ const lockfile = readIndexLockfile(cwd);
7786
+ const repairIds = options.repair ? new Set(createStatusReport({ cwd, targetFile: options.output }).indexes.filter((index) => index.lockfileEntry && index.health !== "ok").map((index) => index.id)) : null;
7787
+ const entries = lockfile.indexes.filter((entry) => {
7788
+ if (options.output && entry.targetFile !== options.output)
7789
+ return false;
7790
+ if (options.kind && entry.kind !== options.kind)
7791
+ return false;
7792
+ if (options.provider && entry.source.name !== options.provider && entry.marker !== options.provider)
7793
+ return false;
7794
+ if (repairIds && !repairIds.has(entry.id))
7795
+ return false;
7796
+ return true;
7797
+ });
7798
+ if (entries.length === 0) {
7799
+ console.log(import_picocolors2.default.yellow(`
7800
+ No lockfile-backed indexes matched refresh filters.
7801
+ `));
7802
+ return;
7803
+ }
7804
+ for (const entry of entries) {
7805
+ if (options.force && entry.kind === "docs") {
7806
+ const cachePath = path.isAbsolute(entry.cachePath) ? entry.cachePath : path.join(cwd, entry.cachePath);
7807
+ if (fs.existsSync(cachePath)) {
7808
+ fs.rmSync(cachePath, { recursive: true, force: true });
7809
+ }
7810
+ }
7811
+ if (entry.kind === "skills") {
7812
+ if (entry.source.type === "skills-sh" && entry.source.repo) {
7813
+ await runSkillsEmbed({ repo: entry.source.repo, output: entry.targetFile });
7814
+ } else {
7815
+ await runSkillsEmbed({ output: entry.targetFile });
7816
+ }
7817
+ continue;
7818
+ }
7819
+ if (entry.source.type === "local-docs" && entry.source.docsPath) {
7820
+ await runLocal(entry.source.docsPath, {
7821
+ name: entry.source.displayName || entry.source.name,
7822
+ output: entry.targetFile
7823
+ });
7824
+ continue;
7825
+ }
7826
+ if (entry.source.type === "url-docs" && entry.source.url) {
7827
+ await runUrl(entry.source.url, {
7828
+ name: entry.source.displayName || entry.source.name,
7829
+ output: entry.targetFile,
7830
+ global: path.isAbsolute(entry.cachePath)
7831
+ });
7832
+ continue;
7833
+ }
7834
+ const provider = entry.source.type === "builtin-provider" ? getProvider(entry.source.name) : entry.source.repo && entry.source.docsPath ? createProvider({
7835
+ name: entry.source.name,
7836
+ displayName: entry.source.displayName || entry.source.name,
7837
+ repo: entry.source.repo,
7838
+ docsPath: entry.source.docsPath
7839
+ }) : null;
7840
+ if (!provider) {
7841
+ console.log(import_picocolors2.default.yellow(`Skipped ${entry.id}: source metadata is incomplete.`));
7842
+ continue;
7843
+ }
7844
+ const version = entry.source.versionMode === "pinned" ? entry.source.version : undefined;
7845
+ await executeEmbed(cwd, provider, version, entry.targetFile, path.isAbsolute(entry.cachePath), undefined, entry.source.versionMode);
7846
+ }
7847
+ }
7848
+ program2.command("refresh").description("Refresh lockfile-backed indexes").option("-o, --output <file>", "Refresh indexes in one agent instruction file").option("-p, --provider <name>", "Refresh one provider or marker name").option("--kind <kind>", "Refresh only docs or skills indexes").option("--force", "Delete existing documentation caches before refreshing").option("--repair", "Refresh only unhealthy lockfile-backed indexes").action(runRefresh);
7849
+ function runMigrate(options) {
7850
+ const cwd = process.cwd();
7851
+ const targets = options.output ? [options.output] : ["AGENTS.md", "AGENTS.local.md", "CLAUDE.md", "CLAUDE.local.md"];
7852
+ const migrated = [];
7853
+ const skipped = [];
7854
+ for (const targetFile of targets) {
7855
+ const targetPath = path.join(cwd, targetFile);
7856
+ if (!fs.existsSync(targetPath))
7857
+ continue;
7858
+ const content = fs.readFileSync(targetPath, "utf-8");
7859
+ const regex = /<!-- AGENTS-MD-EMBED-START:(\S+?) -->\n([\s\S]*?)\n<!-- AGENTS-MD-EMBED-END:\1 -->/g;
7860
+ let match;
7861
+ while ((match = regex.exec(content)) !== null) {
7862
+ const marker = match[1];
7863
+ const block = match[2];
7864
+ const rootMatch = block.match(/(?:^|\|)root:\s*([^|]+)/);
7865
+ if (!rootMatch) {
7866
+ skipped.push({ targetFile, marker, reason: "missing root metadata" });
7867
+ continue;
7868
+ }
7869
+ const cachePath = rootMatch[1].trim().replace(/^\.\//, "");
7870
+ const provider = getProvider(marker);
7871
+ const source = provider ? {
7872
+ type: "builtin-provider",
7873
+ name: provider.name,
7874
+ displayName: provider.displayName,
7875
+ repo: provider.repo,
7876
+ docsPath: provider.docsPath,
7877
+ versionMode: "unknown"
7878
+ } : {
7879
+ type: "local-docs",
7880
+ name: marker,
7881
+ displayName: marker,
7882
+ docsPath: cachePath,
7883
+ versionMode: "unknown"
7884
+ };
7885
+ const entry = upsertIndexLockEntry(cwd, {
7886
+ id: createIndexId("docs", marker, targetFile),
7887
+ kind: "docs",
7888
+ source,
7889
+ targetFile,
7890
+ marker,
7891
+ cachePath,
7892
+ command: `npx agdex --provider ${marker} --output ${targetFile}`
7893
+ });
7894
+ migrated.push(entry.id);
7895
+ }
7896
+ }
7897
+ const result = { migrated, skipped };
7898
+ if (options.json) {
7899
+ console.log(JSON.stringify(result, null, 2));
7900
+ return;
7901
+ }
7902
+ console.log(import_picocolors2.default.cyan(`
7903
+ agdex migrate
7904
+ `));
7905
+ for (const id of migrated) {
7906
+ console.log(`${import_picocolors2.default.green("✓")} Created lockfile entry ${import_picocolors2.default.bold(id)}`);
7907
+ }
7908
+ for (const item of skipped) {
7909
+ console.log(`${import_picocolors2.default.yellow("!")} Skipped ${item.marker} in ${item.targetFile}: ${item.reason}`);
7910
+ }
7911
+ if (migrated.length === 0 && skipped.length === 0) {
7912
+ console.log(import_picocolors2.default.yellow(" No migratable indexes found."));
7913
+ }
7914
+ console.log("");
7915
+ }
7916
+ program2.command("migrate").description("Create lockfile entries from existing embedded markers when safe").option("-o, --output <file>", "Migrate one agent instruction file").option("--json", "Print machine-readable JSON").action(runMigrate);
7684
7917
  async function runRemove(options) {
7685
7918
  const cwd = process.cwd();
7686
7919
  const output = options.output || getDefaultOutput();
@@ -7840,6 +8073,21 @@ Fetching skills from ${import_picocolors2.default.cyan(repoName)}...`);
7840
8073
  const newContent = injectSkillsIndex(existingContent, indexContent);
7841
8074
  fs.writeFileSync(targetPath, newContent, "utf-8");
7842
8075
  const sizeAfter = Buffer.byteLength(newContent, "utf-8");
8076
+ upsertIndexLockEntry(cwd, {
8077
+ id: createIndexId("skills", `skills-sh:${repoName}`, output),
8078
+ kind: "skills",
8079
+ source: {
8080
+ type: "skills-sh",
8081
+ name: `skills-sh:${repoName}`,
8082
+ displayName: repoName,
8083
+ repo: repoName,
8084
+ versionMode: "default-branch"
8085
+ },
8086
+ targetFile: output,
8087
+ marker: "skills",
8088
+ cachePath: cacheDir,
8089
+ command: `npx agdex skills embed --repo ${repoName}`
8090
+ });
7843
8091
  const action2 = isNewFile ? "Created" : "Updated";
7844
8092
  const sizeInfo2 = isNewFile ? formatSize(sizeAfter) : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
7845
8093
  console.log(`${import_picocolors2.default.green("✓")} ${action2} ${import_picocolors2.default.bold(output)} (${sizeInfo2})`);
@@ -7862,6 +8110,20 @@ Discovering skills from ${import_picocolors2.default.cyan(sources.length.toStrin
7862
8110
  const sizeInfo = result.isNewFile ? formatSize(result.sizeAfter) : `${formatSize(result.sizeBefore)} → ${formatSize(result.sizeAfter)}`;
7863
8111
  console.log(`${import_picocolors2.default.green("✓")} ${action} ${import_picocolors2.default.bold(result.targetFile)} (${sizeInfo})`);
7864
8112
  console.log(`${import_picocolors2.default.green("✓")} Indexed ${import_picocolors2.default.bold(result.skillCount.toString())} skills`);
8113
+ upsertIndexLockEntry(cwd, {
8114
+ id: createIndexId("skills", "local-skills", output),
8115
+ kind: "skills",
8116
+ source: {
8117
+ type: "skills-local",
8118
+ name: "local-skills",
8119
+ displayName: "Local skills",
8120
+ versionMode: "unknown"
8121
+ },
8122
+ targetFile: output,
8123
+ marker: "skills",
8124
+ cachePath: cwd,
8125
+ command: "npx agdex skills embed"
8126
+ });
7865
8127
  if (result.sourceBreakdown) {
7866
8128
  const breakdown = [];
7867
8129
  if (result.sourceBreakdown.plugin > 0) {