akm-cli 0.7.5 → 0.8.0-rc.6

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.
Files changed (236) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
  2. package/README.md +20 -4
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli.js +1995 -551
  9. package/dist/commands/agent-dispatch.js +110 -0
  10. package/dist/commands/agent-support.js +68 -0
  11. package/dist/commands/completions.js +3 -0
  12. package/dist/commands/config-cli.js +130 -534
  13. package/dist/commands/consolidate.js +1531 -0
  14. package/dist/commands/curate.js +44 -3
  15. package/dist/commands/db-cli.js +23 -0
  16. package/dist/commands/distill-promotion-policy.js +660 -0
  17. package/dist/commands/distill.js +990 -75
  18. package/dist/commands/eval-cases.js +43 -0
  19. package/dist/commands/events.js +5 -23
  20. package/dist/commands/graph.js +477 -0
  21. package/dist/commands/health.js +400 -0
  22. package/dist/commands/help/help-accept.md +9 -0
  23. package/dist/commands/help/help-improve.md +77 -0
  24. package/dist/commands/help/help-proposals.md +15 -0
  25. package/dist/commands/help/help-propose.md +17 -0
  26. package/dist/commands/help/help-reject.md +8 -0
  27. package/dist/commands/history.js +54 -46
  28. package/dist/commands/improve-profiles.js +146 -0
  29. package/dist/commands/improve-result-file.js +103 -0
  30. package/dist/commands/improve.js +2175 -0
  31. package/dist/commands/info.js +5 -2
  32. package/dist/commands/init.js +50 -2
  33. package/dist/commands/installed-stashes.js +102 -139
  34. package/dist/commands/knowledge.js +136 -0
  35. package/dist/commands/lint/agent-linter.js +49 -0
  36. package/dist/commands/lint/base-linter.js +479 -0
  37. package/dist/commands/lint/command-linter.js +49 -0
  38. package/dist/commands/lint/default-linter.js +16 -0
  39. package/dist/commands/lint/index.js +183 -0
  40. package/dist/commands/lint/knowledge-linter.js +16 -0
  41. package/dist/commands/lint/markdown-insertion.js +343 -0
  42. package/dist/commands/lint/memory-linter.js +61 -0
  43. package/dist/commands/lint/registry.js +36 -0
  44. package/dist/commands/lint/skill-linter.js +45 -0
  45. package/dist/commands/lint/task-linter.js +50 -0
  46. package/dist/commands/lint/types.js +4 -0
  47. package/dist/commands/lint/vault-key-rules.js +139 -0
  48. package/dist/commands/lint/workflow-linter.js +56 -0
  49. package/dist/commands/lint.js +4 -0
  50. package/dist/commands/migration-help.js +5 -2
  51. package/dist/commands/proposal.js +66 -12
  52. package/dist/commands/propose.js +86 -31
  53. package/dist/commands/reflect.js +1119 -73
  54. package/dist/commands/registry-search.js +5 -2
  55. package/dist/commands/remember.js +69 -6
  56. package/dist/commands/schema-repair.js +203 -0
  57. package/dist/commands/search.js +115 -14
  58. package/dist/commands/self-update.js +3 -0
  59. package/dist/commands/show.js +144 -25
  60. package/dist/commands/source-add.js +17 -45
  61. package/dist/commands/source-clone.js +3 -0
  62. package/dist/commands/source-manage.js +14 -19
  63. package/dist/commands/tasks.js +438 -0
  64. package/dist/commands/url-checker.js +42 -0
  65. package/dist/commands/vault.js +130 -77
  66. package/dist/core/action-contributors.js +28 -0
  67. package/dist/core/asset-ref.js +7 -0
  68. package/dist/core/asset-registry.js +7 -16
  69. package/dist/core/asset-serialize.js +88 -0
  70. package/dist/core/asset-spec.js +22 -0
  71. package/dist/core/common.js +157 -0
  72. package/dist/core/concurrent.js +25 -0
  73. package/dist/core/config-io.js +347 -0
  74. package/dist/core/config-migration.js +625 -0
  75. package/dist/core/config-schema.js +501 -0
  76. package/dist/core/config-sources.js +108 -0
  77. package/dist/core/config-types.js +4 -0
  78. package/dist/core/config-walker.js +337 -0
  79. package/dist/core/config.js +327 -987
  80. package/dist/core/errors.js +40 -19
  81. package/dist/core/events.js +91 -138
  82. package/dist/core/file-lock.js +104 -0
  83. package/dist/core/frontmatter.js +3 -6
  84. package/dist/core/lesson-lint.js +3 -0
  85. package/dist/core/markdown.js +20 -0
  86. package/dist/core/memory-belief.js +62 -0
  87. package/dist/core/memory-contradiction-detect.js +274 -0
  88. package/dist/core/memory-improve.js +806 -0
  89. package/dist/core/parse.js +158 -0
  90. package/dist/core/paths.js +326 -14
  91. package/dist/core/proposal-quality-validators.js +364 -0
  92. package/dist/core/proposal-validators.js +69 -0
  93. package/dist/core/proposals.js +498 -42
  94. package/dist/core/state-db.js +927 -0
  95. package/dist/core/text-truncation.js +107 -0
  96. package/dist/core/time.js +54 -0
  97. package/dist/core/warn.js +62 -1
  98. package/dist/core/write-source.js +3 -0
  99. package/dist/indexer/db-backup.js +391 -0
  100. package/dist/indexer/db-search.js +152 -253
  101. package/dist/indexer/db.js +933 -103
  102. package/dist/indexer/ensure-index.js +64 -0
  103. package/dist/indexer/file-context.js +3 -0
  104. package/dist/indexer/graph-boost.js +376 -101
  105. package/dist/indexer/graph-db.js +391 -0
  106. package/dist/indexer/graph-dedup.js +95 -0
  107. package/dist/indexer/graph-extraction.js +550 -124
  108. package/dist/indexer/index-context.js +4 -0
  109. package/dist/indexer/indexer.js +506 -291
  110. package/dist/indexer/llm-cache.js +47 -0
  111. package/dist/indexer/manifest.js +3 -0
  112. package/dist/indexer/matchers.js +148 -160
  113. package/dist/indexer/memory-inference.js +99 -74
  114. package/dist/indexer/metadata-contributors.js +29 -0
  115. package/dist/indexer/metadata.js +255 -196
  116. package/dist/indexer/path-resolver.js +92 -0
  117. package/dist/indexer/project-context.js +192 -0
  118. package/dist/indexer/ranking-contributors.js +331 -0
  119. package/dist/indexer/ranking.js +81 -0
  120. package/dist/indexer/search-fields.js +5 -9
  121. package/dist/indexer/search-hit-enrichers.js +111 -0
  122. package/dist/indexer/search-source.js +44 -10
  123. package/dist/indexer/semantic-status.js +5 -16
  124. package/dist/indexer/staleness-detect.js +447 -0
  125. package/dist/indexer/usage-events.js +12 -9
  126. package/dist/indexer/walker.js +28 -0
  127. package/dist/integrations/agent/builders.js +135 -0
  128. package/dist/integrations/agent/config.js +122 -230
  129. package/dist/integrations/agent/detect.js +3 -0
  130. package/dist/integrations/agent/index.js +7 -13
  131. package/dist/integrations/agent/model-aliases.js +55 -0
  132. package/dist/integrations/agent/profiles.js +70 -5
  133. package/dist/integrations/agent/prompts.js +150 -74
  134. package/dist/integrations/agent/runner.js +151 -0
  135. package/dist/integrations/agent/sdk-runner.js +126 -0
  136. package/dist/integrations/agent/spawn.js +118 -23
  137. package/dist/integrations/github.js +3 -0
  138. package/dist/integrations/lockfile.js +32 -69
  139. package/dist/integrations/session-logs/index.js +68 -0
  140. package/dist/integrations/session-logs/providers/claude-code.js +59 -0
  141. package/dist/integrations/session-logs/providers/opencode.js +55 -0
  142. package/dist/integrations/session-logs/types.js +4 -0
  143. package/dist/llm/call-ai.js +62 -0
  144. package/dist/llm/client.js +72 -124
  145. package/dist/llm/embedder.js +3 -19
  146. package/dist/llm/embedders/cache.js +3 -7
  147. package/dist/llm/embedders/local.js +3 -0
  148. package/dist/llm/embedders/remote.js +20 -8
  149. package/dist/llm/embedders/types.js +3 -7
  150. package/dist/llm/feature-gate.js +89 -48
  151. package/dist/llm/graph-extract.js +676 -70
  152. package/dist/llm/index-passes.js +9 -23
  153. package/dist/llm/memory-infer.js +52 -71
  154. package/dist/llm/metadata-enhance.js +42 -29
  155. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  156. package/dist/output/cli-hints-full.md +281 -0
  157. package/dist/output/cli-hints-short.md +65 -0
  158. package/dist/output/cli-hints.js +5 -318
  159. package/dist/output/context.js +3 -0
  160. package/dist/output/renderers.js +223 -256
  161. package/dist/output/shapes.js +150 -105
  162. package/dist/output/text.js +318 -30
  163. package/dist/registry/build-index.js +3 -0
  164. package/dist/registry/create-provider-registry.js +3 -0
  165. package/dist/registry/factory.js +3 -0
  166. package/dist/registry/origin-resolve.js +3 -0
  167. package/dist/registry/providers/index.js +3 -0
  168. package/dist/registry/providers/skills-sh.js +70 -49
  169. package/dist/registry/providers/static-index.js +53 -48
  170. package/dist/registry/providers/types.js +3 -24
  171. package/dist/registry/resolve.js +11 -16
  172. package/dist/registry/types.js +3 -0
  173. package/dist/scripts/migrate-storage.js +17307 -0
  174. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
  175. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  176. package/dist/setup/detect.js +3 -0
  177. package/dist/setup/ripgrep-install.js +3 -0
  178. package/dist/setup/ripgrep-resolve.js +3 -0
  179. package/dist/setup/setup.js +775 -37
  180. package/dist/setup/steps.js +3 -15
  181. package/dist/sources/include.js +3 -0
  182. package/dist/sources/provider-factory.js +5 -12
  183. package/dist/sources/provider.js +3 -20
  184. package/dist/sources/providers/filesystem.js +19 -23
  185. package/dist/sources/providers/git.js +7 -5
  186. package/dist/sources/providers/index.js +3 -0
  187. package/dist/sources/providers/install-types.js +3 -13
  188. package/dist/sources/providers/npm.js +3 -4
  189. package/dist/sources/providers/provider-utils.js +3 -0
  190. package/dist/sources/providers/sync-from-ref.js +3 -11
  191. package/dist/sources/providers/tar-utils.js +3 -0
  192. package/dist/sources/providers/website.js +18 -22
  193. package/dist/sources/resolve.js +3 -0
  194. package/dist/sources/types.js +3 -0
  195. package/dist/sources/website-ingest.js +7 -0
  196. package/dist/tasks/backends/cron.js +203 -0
  197. package/dist/tasks/backends/exec-utils.js +28 -0
  198. package/dist/tasks/backends/index.js +24 -0
  199. package/dist/tasks/backends/launchd-template.xml +19 -0
  200. package/dist/tasks/backends/launchd.js +187 -0
  201. package/dist/tasks/backends/schtasks-template.xml +29 -0
  202. package/dist/tasks/backends/schtasks.js +215 -0
  203. package/dist/tasks/parser.js +211 -0
  204. package/dist/tasks/resolveAkmBin.js +87 -0
  205. package/dist/tasks/runner.js +458 -0
  206. package/dist/tasks/schedule.js +211 -0
  207. package/dist/tasks/schema.js +15 -0
  208. package/dist/tasks/validator.js +62 -0
  209. package/dist/version.js +3 -0
  210. package/dist/wiki/index-template.md +12 -0
  211. package/dist/wiki/ingest-workflow-template.md +54 -0
  212. package/dist/wiki/log-template.md +8 -0
  213. package/dist/wiki/schema-template.md +61 -0
  214. package/dist/wiki/wiki-templates.js +15 -0
  215. package/dist/wiki/wiki.js +13 -61
  216. package/dist/workflows/authoring.js +8 -25
  217. package/dist/workflows/cli.js +3 -0
  218. package/dist/workflows/db.js +140 -10
  219. package/dist/workflows/document-cache.js +3 -10
  220. package/dist/workflows/parser.js +3 -0
  221. package/dist/workflows/renderer.js +11 -3
  222. package/dist/workflows/runs.js +62 -91
  223. package/dist/workflows/schema.js +3 -0
  224. package/dist/workflows/scope-key.js +3 -0
  225. package/dist/workflows/validator.js +4 -8
  226. package/dist/workflows/workflow-template.md +24 -0
  227. package/docs/README.md +9 -2
  228. package/docs/data-and-telemetry.md +225 -0
  229. package/docs/migration/release-notes/0.7.0.md +1 -1
  230. package/docs/migration/release-notes/0.7.5.md +2 -2
  231. package/docs/migration/release-notes/0.8.0.md +48 -0
  232. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  233. package/package.json +20 -8
  234. package/.github/LICENSE +0 -374
  235. package/dist/commands/install-audit.js +0 -381
  236. package/dist/templates/wiki-templates.js +0 -100
@@ -1,13 +1,13 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
3
4
  import { fetchWithRetry } from "../../core/common";
4
- import { getRegistryIndexCacheDir } from "../../core/paths";
5
+ import { rethrowIfTestIsolationError } from "../../core/errors";
6
+ import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
5
7
  import { registerProvider } from "../factory";
6
8
  // ── Constants ───────────────────────────────────────────────────────────────
7
9
  /** Per-query cache TTL in milliseconds (15 minutes). */
8
10
  const QUERY_CACHE_TTL_MS = 15 * 60 * 1000;
9
- /** Maximum age before query cache is considered stale but still usable (1 day). */
10
- const QUERY_CACHE_STALE_MS = 24 * 60 * 60 * 1000;
11
11
  // ── Provider class ──────────────────────────────────────────────────────────
12
12
  class SkillsShProvider {
13
13
  type = "skills-sh";
@@ -89,13 +89,38 @@ class SkillsShProvider {
89
89
  return ref.source === "github";
90
90
  }
91
91
  async fetchSkills(query, limit) {
92
- // Check per-query cache first
93
- const cachePath = this.queryCachePath(query, limit);
94
- const cached = this.readQueryCache(cachePath);
95
- if (cached && !isExpired(cached.mtime, QUERY_CACHE_TTL_MS)) {
96
- return cached.entries;
92
+ // Build a stable DB cache key for this query
93
+ const dbCacheKey = this.queryDbCacheKey(query, limit);
94
+ // ── Step 1: Try DB cache (index.db) ───────────────────────────────────
95
+ let db;
96
+ let dbCacheResult;
97
+ try {
98
+ db = openDatabase();
99
+ dbCacheResult = getRegistryIndexCache(db, dbCacheKey, QUERY_CACHE_TTL_MS);
100
+ }
101
+ catch (err) {
102
+ // Never mask the bun-test isolation guard as "DB unavailable" — see
103
+ // rethrowIfTestIsolationError in src/core/errors.ts. Without this,
104
+ // a leaky test silently gets a cold cache + fresh fetch instead of
105
+ // the loud TEST_ISOLATION_MISSING failure the guard intends.
106
+ rethrowIfTestIsolationError(err);
107
+ // index.db not available yet (pre-migration install or test env) — fall through
97
108
  }
98
- // Fetch from API
109
+ if (dbCacheResult) {
110
+ try {
111
+ const parsed = JSON.parse(dbCacheResult.indexJson);
112
+ if (Array.isArray(parsed)) {
113
+ const entries = parsed.filter(isValidSkillsEntry);
114
+ if (db)
115
+ closeDatabase(db);
116
+ return entries;
117
+ }
118
+ }
119
+ catch {
120
+ /* corrupt DB entry — fall through */
121
+ }
122
+ }
123
+ // ── Step 2: Fetch from API ─────────────────────────────────────────────
99
124
  const baseUrl = this.config.url.replace(/\/+$/, "");
100
125
  const url = `${baseUrl}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
101
126
  try {
@@ -105,13 +130,40 @@ class SkillsShProvider {
105
130
  }
106
131
  const data = (await response.json());
107
132
  const entries = parseSkillsResponse(data);
108
- this.writeQueryCache(cachePath, entries);
133
+ // Write to DB cache (primary)
134
+ if (db) {
135
+ try {
136
+ upsertRegistryIndexCache(db, dbCacheKey, JSON.stringify(entries));
137
+ }
138
+ catch {
139
+ /* best-effort */
140
+ }
141
+ closeDatabase(db);
142
+ }
109
143
  return entries;
110
144
  }
111
145
  catch (err) {
112
- // Fall back to stale cache if available
113
- if (cached && !isExpired(cached.mtime, QUERY_CACHE_STALE_MS)) {
114
- return cached.entries;
146
+ if (db) {
147
+ try {
148
+ closeDatabase(db);
149
+ }
150
+ catch {
151
+ /* ignore */
152
+ }
153
+ }
154
+ // Fetch failed — use stale DB cache if available
155
+ if (dbCacheResult) {
156
+ try {
157
+ const parsed = JSON.parse(dbCacheResult.indexJson);
158
+ if (Array.isArray(parsed)) {
159
+ const entries = parsed.filter(isValidSkillsEntry);
160
+ if (entries.length > 0)
161
+ return entries;
162
+ }
163
+ }
164
+ catch {
165
+ /* ignore */
166
+ }
115
167
  }
116
168
  throw err;
117
169
  }
@@ -167,9 +219,8 @@ class SkillsShProvider {
167
219
  });
168
220
  return hits.length > 0 ? hits : undefined;
169
221
  }
170
- // ── Per-query cache ─────────────────────────────────────────────────────
171
- queryCachePath(query, limit) {
172
- const cacheDir = getRegistryIndexCacheDir();
222
+ // ── DB cache key ────────────────────────────────────────────────────────
223
+ queryDbCacheKey(query, limit) {
173
224
  const hasher = new Bun.CryptoHasher("md5");
174
225
  hasher.update(this.config.url);
175
226
  hasher.update("\0");
@@ -177,33 +228,7 @@ class SkillsShProvider {
177
228
  hasher.update("\0");
178
229
  hasher.update(String(limit));
179
230
  const hash = hasher.digest("hex");
180
- return path.join(cacheDir, `skills-sh-search-${hash}.json`);
181
- }
182
- readQueryCache(cachePath) {
183
- try {
184
- const stat = fs.statSync(cachePath);
185
- const raw = JSON.parse(fs.readFileSync(cachePath, "utf8"));
186
- if (!Array.isArray(raw))
187
- return null;
188
- const entries = raw.filter(isValidSkillsEntry);
189
- return { entries, mtime: stat.mtimeMs };
190
- }
191
- catch {
192
- return null;
193
- }
194
- }
195
- writeQueryCache(cachePath, entries) {
196
- try {
197
- const dir = path.dirname(cachePath);
198
- fs.mkdirSync(dir, { recursive: true });
199
- const tmpPath = `${cachePath}.tmp.${process.pid}`;
200
- // 0o600: owner read/write only — cache may contain search terms tied to API keys
201
- fs.writeFileSync(tmpPath, JSON.stringify(entries), { encoding: "utf8", mode: 0o600 });
202
- fs.renameSync(tmpPath, cachePath);
203
- }
204
- catch {
205
- // Best-effort caching
206
- }
231
+ return `skills-sh:${hash}`;
207
232
  }
208
233
  }
209
234
  // ── Self-register ───────────────────────────────────────────────────────────
@@ -226,7 +251,3 @@ function isValidSkillsEntry(entry) {
226
251
  typeof obj.installs === "number" &&
227
252
  typeof obj.source === "string");
228
253
  }
229
- // ── Utilities ───────────────────────────────────────────────────────────────
230
- function isExpired(mtimeMs, ttlMs) {
231
- return Date.now() - mtimeMs > ttlMs;
232
- }
@@ -1,7 +1,9 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
3
4
  import { fetchWithRetry, jsonWithByteCap, toErrorMessage } from "../../core/common";
4
- import { getRegistryIndexCacheDir } from "../../core/paths";
5
+ import { rethrowIfTestIsolationError } from "../../core/errors";
6
+ import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
5
7
  import { asString } from "../../integrations/github";
6
8
  import { registerProvider } from "../factory";
7
9
  // ── Constants ───────────────────────────────────────────────────────────────
@@ -114,13 +116,30 @@ function assetHitToPreview(hit) {
114
116
  registerProvider("static-index", (config) => new StaticIndexProvider(config));
115
117
  // ── Index loading with cache ────────────────────────────────────────────────
116
118
  async function loadIndex(entry) {
117
- const cachePath = indexCachePath(entry.url);
118
- const cached = readCachedIndex(cachePath);
119
- // Fresh cache: return immediately
120
- if (cached && !isCacheExpired(cached.mtime)) {
121
- return cached.index;
119
+ // ── Step 1: Try DB cache (index.db) ─────────────────────────────────────
120
+ let db;
121
+ let dbCacheResult;
122
+ try {
123
+ db = openDatabase();
124
+ dbCacheResult = getRegistryIndexCache(db, entry.url, CACHE_TTL_MS);
125
+ }
126
+ catch (err) {
127
+ // Never mask the bun-test isolation guard as "DB unavailable" — see
128
+ // rethrowIfTestIsolationError in src/core/errors.ts. Without this, a
129
+ // leaky test silently gets a cold cache instead of the loud
130
+ // TEST_ISOLATION_MISSING failure the guard intends.
131
+ rethrowIfTestIsolationError(err);
132
+ // index.db not available yet (pre-migration install or test env) — fall through
133
+ }
134
+ if (dbCacheResult) {
135
+ const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
136
+ if (index) {
137
+ if (db)
138
+ closeDatabase(db);
139
+ return index;
140
+ }
122
141
  }
123
- // Try to fetch fresh index
142
+ // ── Step 2: Fetch fresh index from remote ────────────────────────────────
124
143
  try {
125
144
  const response = await fetchWithRetry(entry.url, undefined, { timeout: 10_000 });
126
145
  if (!response.ok) {
@@ -131,54 +150,40 @@ async function loadIndex(entry) {
131
150
  const data = await jsonWithByteCap(response, 50 * 1024 * 1024);
132
151
  const index = parseRegistryIndex(data);
133
152
  if (index) {
134
- writeCachedIndex(cachePath, index);
153
+ // Write to DB cache (primary)
154
+ if (db) {
155
+ try {
156
+ const etag = response.headers.get("etag") ?? undefined;
157
+ const lastModified = response.headers.get("last-modified") ?? undefined;
158
+ upsertRegistryIndexCache(db, entry.url, JSON.stringify(index), { etag, lastModified });
159
+ }
160
+ catch {
161
+ /* best-effort */
162
+ }
163
+ closeDatabase(db);
164
+ }
135
165
  return index;
136
166
  }
137
167
  throw new Error("Invalid registry index format");
138
168
  }
139
169
  catch (err) {
140
- // Fetch failed — use stale cache if available
141
- if (cached && !isCacheStale(cached.mtime)) {
142
- return cached.index;
170
+ if (db) {
171
+ try {
172
+ closeDatabase(db);
173
+ }
174
+ catch {
175
+ /* ignore */
176
+ }
177
+ }
178
+ // Fetch failed — use stale DB cache if available
179
+ if (dbCacheResult) {
180
+ const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
181
+ if (index)
182
+ return index;
143
183
  }
144
184
  throw err;
145
185
  }
146
186
  }
147
- // ── Cache helpers (exported for reuse by other providers) ───────────────────
148
- export function indexCachePath(url) {
149
- const indexDir = getRegistryIndexCacheDir();
150
- // Deterministic filename from URL
151
- const slug = url
152
- .replace(/[^a-zA-Z0-9]+/g, "-")
153
- .replace(/^-+|-+$/g, "")
154
- .slice(0, 120);
155
- return path.join(indexDir, `${slug}.json`);
156
- }
157
- export function readCachedIndex(cachePath) {
158
- try {
159
- const stat = fs.statSync(cachePath);
160
- const raw = JSON.parse(fs.readFileSync(cachePath, "utf8"));
161
- const index = parseRegistryIndex(raw);
162
- if (!index)
163
- return null;
164
- return { index, mtime: stat.mtimeMs };
165
- }
166
- catch {
167
- return null;
168
- }
169
- }
170
- export function writeCachedIndex(cachePath, index) {
171
- try {
172
- const dir = path.dirname(cachePath);
173
- fs.mkdirSync(dir, { recursive: true });
174
- const tmpPath = `${cachePath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
175
- fs.writeFileSync(tmpPath, JSON.stringify(index), "utf8");
176
- fs.renameSync(tmpPath, cachePath);
177
- }
178
- catch {
179
- // Best-effort caching — don't fail the search if we can't write
180
- }
181
- }
182
187
  export function isCacheExpired(mtimeMs) {
183
188
  return Date.now() - mtimeMs > CACHE_TTL_MS;
184
189
  }
@@ -1,25 +1,4 @@
1
- /**
2
- * Registry provider interface (v1 architecture spec §3.1).
3
- *
4
- * A `RegistryProvider` is a read-only catalog that lists installable kits and
5
- * (optionally) previews assets within them. It is *not* a `SourceProvider`:
6
- * registry providers do not materialise files to disk — they only answer
7
- * discovery queries.
8
- *
9
- * The two built-in registry providers at v1 are:
10
- *
11
- * - `static-index` — reads the v2 JSON index schema (the official akm registry
12
- * and any static-hosted team registry). The v2 schema is owned by this
13
- * provider, not by core akm.
14
- * - `skills-sh` — wraps the skills.sh REST API.
15
- *
16
- * Context Hub is **not** a registry provider — it is an ordinary git repository
17
- * recommended via the official static-index registry (see CLAUDE.md).
18
- *
19
- * Note: the simple `search()` method is the v0.6 surface and remains the
20
- * primary entry point used by the orchestrator. The `searchKits` /
21
- * `searchAssets` / `getKit` / `canHandle` methods are the v1-spec contract
22
- * (§3.1) which built-in providers also implement so the orchestrator can be
23
- * iterated cleanly post-Phase 6 without reaching into provider-specific shapes.
24
- */
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
25
4
  export {};
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  import { spawnSync } from "node:child_process";
2
5
  import fs from "node:fs";
3
6
  import os from "node:os";
@@ -340,6 +343,7 @@ async function resolveNpmArtifact(parsed) {
340
343
  }
341
344
  async function resolveGithubArtifact(parsed) {
342
345
  const gitUrl = `https://github.com/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}.git`;
346
+ const repoBase = `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`;
343
347
  // Prefer git-backed installs so private GitHub repos work with the user's
344
348
  // normal git credential helper rather than requiring API-specific auth.
345
349
  const gitResolvedRevision = resolveGitRevisionFromRemote(gitUrl, parsed.requestedRef);
@@ -355,18 +359,18 @@ async function resolveGithubArtifact(parsed) {
355
359
  }
356
360
  const headers = githubHeaders();
357
361
  if (parsed.requestedRef) {
358
- const commit = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(parsed.requestedRef)}`, headers);
362
+ const commit = await tryFetchJson(`${repoBase}/commits/${encodeURIComponent(parsed.requestedRef)}`, headers);
359
363
  const resolvedRevision = asString(commit?.sha) ?? parsed.requestedRef;
360
364
  return {
361
365
  id: parsed.id,
362
366
  source: parsed.source,
363
367
  ref: parsed.ref,
364
- artifactUrl: `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/tarball/${encodeURIComponent(parsed.requestedRef)}`,
368
+ artifactUrl: `${repoBase}/tarball/${encodeURIComponent(parsed.requestedRef)}`,
365
369
  resolvedRevision,
366
370
  resolvedVersion: parsed.requestedRef,
367
371
  };
368
372
  }
369
- const latestRelease = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/releases/latest`, headers);
373
+ const latestRelease = await tryFetchJson(`${repoBase}/releases/latest`, headers);
370
374
  if (latestRelease) {
371
375
  const tarballUrl = asString(latestRelease.tarball_url);
372
376
  if (tarballUrl) {
@@ -380,17 +384,17 @@ async function resolveGithubArtifact(parsed) {
380
384
  };
381
385
  }
382
386
  }
383
- const repoMeta = await fetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`, headers);
387
+ const repoMeta = await fetchJson(repoBase, headers);
384
388
  const defaultBranch = asString(repoMeta.default_branch);
385
389
  if (!defaultBranch) {
386
390
  throw new Error(`Unable to resolve default branch for ${parsed.owner}/${parsed.repo}.`);
387
391
  }
388
- const commit = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(defaultBranch)}`, headers);
392
+ const commit = await tryFetchJson(`${repoBase}/commits/${encodeURIComponent(defaultBranch)}`, headers);
389
393
  return {
390
394
  id: parsed.id,
391
395
  source: parsed.source,
392
396
  ref: parsed.ref,
393
- artifactUrl: `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/tarball/${encodeURIComponent(defaultBranch)}`,
397
+ artifactUrl: `${repoBase}/tarball/${encodeURIComponent(defaultBranch)}`,
394
398
  resolvedVersion: defaultBranch,
395
399
  resolvedRevision: asString(commit?.sha) ?? defaultBranch,
396
400
  };
@@ -407,16 +411,7 @@ function resolveGitRevisionFromRemote(url, requestedRef) {
407
411
  return firstLine?.split(/\s/)[0] || undefined;
408
412
  }
409
413
  async function resolveGitArtifact(parsed) {
410
- validateGitUrl(parsed.url);
411
- const ref = parsed.requestedRef ?? "HEAD";
412
- if (parsed.requestedRef)
413
- validateGitRef(parsed.requestedRef);
414
- const result = spawnSync("git", ["ls-remote", parsed.url, ref], { encoding: "utf8", timeout: 30_000 });
415
- let resolvedRevision;
416
- if (result.status === 0) {
417
- const firstLine = result.stdout.trim().split(/\r?\n/)[0];
418
- resolvedRevision = firstLine?.split(/\s/)[0] || undefined;
419
- }
414
+ const resolvedRevision = resolveGitRevisionFromRemote(parsed.url, parsed.requestedRef);
420
415
  return {
421
416
  id: parsed.id,
422
417
  source: parsed.source,
@@ -1 +1,4 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  export {};