akm-cli 0.7.5 → 0.8.0-rc.3

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 (155) hide show
  1. package/.github/CHANGELOG.md +1 -1
  2. package/dist/cli/parse-args.js +86 -0
  3. package/dist/cli.js +1023 -521
  4. package/dist/commands/agent-dispatch.js +107 -0
  5. package/dist/commands/agent-support.js +62 -0
  6. package/dist/commands/config-cli.js +68 -84
  7. package/dist/commands/consolidate.js +812 -0
  8. package/dist/commands/distill-promotion-policy.js +658 -0
  9. package/dist/commands/distill.js +218 -43
  10. package/dist/commands/eval-cases.js +40 -0
  11. package/dist/commands/events.js +2 -23
  12. package/dist/commands/graph.js +222 -0
  13. package/dist/commands/health.js +376 -0
  14. package/dist/commands/help/help-accept.md +9 -0
  15. package/dist/commands/help/help-improve.md +53 -0
  16. package/dist/commands/help/help-proposals.md +15 -0
  17. package/dist/commands/help/help-propose.md +17 -0
  18. package/dist/commands/help/help-reject.md +8 -0
  19. package/dist/commands/history.js +3 -30
  20. package/dist/commands/improve.js +1161 -0
  21. package/dist/commands/info.js +2 -2
  22. package/dist/commands/init.js +2 -2
  23. package/dist/commands/install-audit.js +5 -1
  24. package/dist/commands/installed-stashes.js +118 -138
  25. package/dist/commands/knowledge.js +133 -0
  26. package/dist/commands/lint/agent-linter.js +46 -0
  27. package/dist/commands/lint/base-linter.js +291 -0
  28. package/dist/commands/lint/command-linter.js +46 -0
  29. package/dist/commands/lint/default-linter.js +13 -0
  30. package/dist/commands/lint/index.js +145 -0
  31. package/dist/commands/lint/knowledge-linter.js +13 -0
  32. package/dist/commands/lint/memory-linter.js +58 -0
  33. package/dist/commands/lint/registry.js +33 -0
  34. package/dist/commands/lint/skill-linter.js +42 -0
  35. package/dist/commands/lint/task-linter.js +47 -0
  36. package/dist/commands/lint/types.js +1 -0
  37. package/dist/commands/lint/vault-key-rules.js +67 -0
  38. package/dist/commands/lint/workflow-linter.js +53 -0
  39. package/dist/commands/lint.js +1 -0
  40. package/dist/commands/proposal.js +8 -7
  41. package/dist/commands/propose.js +71 -28
  42. package/dist/commands/reflect.js +135 -35
  43. package/dist/commands/registry-search.js +2 -2
  44. package/dist/commands/remember.js +54 -0
  45. package/dist/commands/schema-repair.js +130 -0
  46. package/dist/commands/search.js +21 -5
  47. package/dist/commands/show.js +125 -20
  48. package/dist/commands/source-add.js +10 -10
  49. package/dist/commands/source-manage.js +11 -19
  50. package/dist/commands/tasks.js +385 -0
  51. package/dist/commands/url-checker.js +39 -0
  52. package/dist/commands/vault.js +168 -77
  53. package/dist/core/action-contributors.js +25 -0
  54. package/dist/core/asset-ref.js +4 -0
  55. package/dist/core/asset-registry.js +4 -16
  56. package/dist/core/asset-spec.js +10 -0
  57. package/dist/core/common.js +100 -0
  58. package/dist/core/concurrent.js +22 -0
  59. package/dist/core/config.js +233 -133
  60. package/dist/core/events.js +73 -126
  61. package/dist/core/frontmatter.js +0 -6
  62. package/dist/core/markdown.js +17 -0
  63. package/dist/core/memory-improve.js +678 -0
  64. package/dist/core/parse.js +155 -0
  65. package/dist/core/paths.js +101 -3
  66. package/dist/core/proposal-validators.js +61 -0
  67. package/dist/core/proposals.js +49 -38
  68. package/dist/core/state-db.js +731 -0
  69. package/dist/core/time.js +51 -0
  70. package/dist/core/warn.js +59 -1
  71. package/dist/indexer/db-search.js +52 -238
  72. package/dist/indexer/db.js +403 -54
  73. package/dist/indexer/ensure-index.js +61 -0
  74. package/dist/indexer/graph-boost.js +247 -94
  75. package/dist/indexer/graph-db.js +201 -0
  76. package/dist/indexer/graph-dedup.js +99 -0
  77. package/dist/indexer/graph-extraction.js +409 -76
  78. package/dist/indexer/index-context.js +10 -0
  79. package/dist/indexer/indexer.js +456 -290
  80. package/dist/indexer/llm-cache.js +47 -0
  81. package/dist/indexer/matchers.js +124 -160
  82. package/dist/indexer/memory-inference.js +63 -29
  83. package/dist/indexer/metadata-contributors.js +26 -0
  84. package/dist/indexer/metadata.js +196 -197
  85. package/dist/indexer/path-resolver.js +89 -0
  86. package/dist/indexer/ranking-contributors.js +204 -0
  87. package/dist/indexer/ranking.js +74 -0
  88. package/dist/indexer/search-hit-enrichers.js +22 -0
  89. package/dist/indexer/search-source.js +24 -9
  90. package/dist/indexer/semantic-status.js +2 -16
  91. package/dist/indexer/walker.js +25 -0
  92. package/dist/integrations/agent/builders.js +109 -0
  93. package/dist/integrations/agent/config.js +203 -3
  94. package/dist/integrations/agent/index.js +5 -2
  95. package/dist/integrations/agent/model-aliases.js +63 -0
  96. package/dist/integrations/agent/profiles.js +67 -5
  97. package/dist/integrations/agent/prompts.js +77 -72
  98. package/dist/integrations/agent/sdk-runner.js +120 -0
  99. package/dist/integrations/agent/spawn.js +93 -22
  100. package/dist/integrations/lockfile.js +10 -18
  101. package/dist/integrations/session-logs/index.js +65 -0
  102. package/dist/integrations/session-logs/providers/claude-code.js +56 -0
  103. package/dist/integrations/session-logs/providers/opencode.js +52 -0
  104. package/dist/integrations/session-logs/types.js +1 -0
  105. package/dist/llm/call-ai.js +74 -0
  106. package/dist/llm/client.js +61 -122
  107. package/dist/llm/feature-gate.js +27 -16
  108. package/dist/llm/graph-extract.js +297 -62
  109. package/dist/llm/memory-infer.js +49 -71
  110. package/dist/llm/metadata-enhance.js +39 -22
  111. package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
  112. package/dist/output/cli-hints-full.md +277 -0
  113. package/dist/output/cli-hints-short.md +65 -0
  114. package/dist/output/cli-hints.js +2 -318
  115. package/dist/output/renderers.js +220 -256
  116. package/dist/output/shapes.js +101 -93
  117. package/dist/output/text.js +256 -17
  118. package/dist/registry/providers/skills-sh.js +61 -49
  119. package/dist/registry/providers/static-index.js +44 -48
  120. package/dist/registry/resolve.js +8 -16
  121. package/dist/setup/setup.js +510 -11
  122. package/dist/sources/provider-factory.js +2 -1
  123. package/dist/sources/providers/filesystem.js +16 -23
  124. package/dist/sources/providers/git.js +4 -5
  125. package/dist/sources/providers/website.js +15 -22
  126. package/dist/sources/website-ingest.js +4 -0
  127. package/dist/tasks/backends/cron.js +200 -0
  128. package/dist/tasks/backends/exec-utils.js +25 -0
  129. package/dist/tasks/backends/index.js +32 -0
  130. package/dist/tasks/backends/launchd-template.xml +19 -0
  131. package/dist/tasks/backends/launchd.js +184 -0
  132. package/dist/tasks/backends/schtasks-template.xml +29 -0
  133. package/dist/tasks/backends/schtasks.js +212 -0
  134. package/dist/tasks/parser.js +198 -0
  135. package/dist/tasks/resolveAkmBin.js +84 -0
  136. package/dist/tasks/runner.js +432 -0
  137. package/dist/tasks/schedule.js +208 -0
  138. package/dist/tasks/schema.js +13 -0
  139. package/dist/tasks/validator.js +59 -0
  140. package/dist/wiki/index-template.md +12 -0
  141. package/dist/wiki/ingest-workflow-template.md +54 -0
  142. package/dist/wiki/log-template.md +8 -0
  143. package/dist/wiki/schema-template.md +61 -0
  144. package/dist/wiki/wiki-templates.js +12 -0
  145. package/dist/wiki/wiki.js +10 -61
  146. package/dist/workflows/authoring.js +5 -25
  147. package/dist/workflows/renderer.js +8 -3
  148. package/dist/workflows/runs.js +59 -91
  149. package/dist/workflows/validator.js +1 -1
  150. package/dist/workflows/workflow-template.md +24 -0
  151. package/docs/README.md +5 -2
  152. package/docs/migration/release-notes/0.7.0.md +1 -1
  153. package/docs/migration/release-notes/0.8.0.md +43 -0
  154. package/package.json +3 -2
  155. package/dist/templates/wiki-templates.js +0 -100
@@ -1,13 +1,9 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import { fetchWithRetry } from "../../core/common";
4
- import { getRegistryIndexCacheDir } from "../../core/paths";
2
+ import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
5
3
  import { registerProvider } from "../factory";
6
4
  // ── Constants ───────────────────────────────────────────────────────────────
7
5
  /** Per-query cache TTL in milliseconds (15 minutes). */
8
6
  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
7
  // ── Provider class ──────────────────────────────────────────────────────────
12
8
  class SkillsShProvider {
13
9
  type = "skills-sh";
@@ -89,13 +85,33 @@ class SkillsShProvider {
89
85
  return ref.source === "github";
90
86
  }
91
87
  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;
88
+ // Build a stable DB cache key for this query
89
+ const dbCacheKey = this.queryDbCacheKey(query, limit);
90
+ // ── Step 1: Try DB cache (index.db) ───────────────────────────────────
91
+ let db;
92
+ let dbCacheResult;
93
+ try {
94
+ db = openDatabase();
95
+ dbCacheResult = getRegistryIndexCache(db, dbCacheKey, QUERY_CACHE_TTL_MS);
96
+ }
97
+ catch {
98
+ // index.db not available yet (pre-migration install or test env) — fall through
99
+ }
100
+ if (dbCacheResult) {
101
+ try {
102
+ const parsed = JSON.parse(dbCacheResult.indexJson);
103
+ if (Array.isArray(parsed)) {
104
+ const entries = parsed.filter(isValidSkillsEntry);
105
+ if (db)
106
+ closeDatabase(db);
107
+ return entries;
108
+ }
109
+ }
110
+ catch {
111
+ /* corrupt DB entry — fall through */
112
+ }
97
113
  }
98
- // Fetch from API
114
+ // ── Step 2: Fetch from API ─────────────────────────────────────────────
99
115
  const baseUrl = this.config.url.replace(/\/+$/, "");
100
116
  const url = `${baseUrl}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
101
117
  try {
@@ -105,13 +121,40 @@ class SkillsShProvider {
105
121
  }
106
122
  const data = (await response.json());
107
123
  const entries = parseSkillsResponse(data);
108
- this.writeQueryCache(cachePath, entries);
124
+ // Write to DB cache (primary)
125
+ if (db) {
126
+ try {
127
+ upsertRegistryIndexCache(db, dbCacheKey, JSON.stringify(entries));
128
+ }
129
+ catch {
130
+ /* best-effort */
131
+ }
132
+ closeDatabase(db);
133
+ }
109
134
  return entries;
110
135
  }
111
136
  catch (err) {
112
- // Fall back to stale cache if available
113
- if (cached && !isExpired(cached.mtime, QUERY_CACHE_STALE_MS)) {
114
- return cached.entries;
137
+ if (db) {
138
+ try {
139
+ closeDatabase(db);
140
+ }
141
+ catch {
142
+ /* ignore */
143
+ }
144
+ }
145
+ // Fetch failed — use stale DB cache if available
146
+ if (dbCacheResult) {
147
+ try {
148
+ const parsed = JSON.parse(dbCacheResult.indexJson);
149
+ if (Array.isArray(parsed)) {
150
+ const entries = parsed.filter(isValidSkillsEntry);
151
+ if (entries.length > 0)
152
+ return entries;
153
+ }
154
+ }
155
+ catch {
156
+ /* ignore */
157
+ }
115
158
  }
116
159
  throw err;
117
160
  }
@@ -167,9 +210,8 @@ class SkillsShProvider {
167
210
  });
168
211
  return hits.length > 0 ? hits : undefined;
169
212
  }
170
- // ── Per-query cache ─────────────────────────────────────────────────────
171
- queryCachePath(query, limit) {
172
- const cacheDir = getRegistryIndexCacheDir();
213
+ // ── DB cache key ────────────────────────────────────────────────────────
214
+ queryDbCacheKey(query, limit) {
173
215
  const hasher = new Bun.CryptoHasher("md5");
174
216
  hasher.update(this.config.url);
175
217
  hasher.update("\0");
@@ -177,33 +219,7 @@ class SkillsShProvider {
177
219
  hasher.update("\0");
178
220
  hasher.update(String(limit));
179
221
  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
- }
222
+ return `skills-sh:${hash}`;
207
223
  }
208
224
  }
209
225
  // ── Self-register ───────────────────────────────────────────────────────────
@@ -226,7 +242,3 @@ function isValidSkillsEntry(entry) {
226
242
  typeof obj.installs === "number" &&
227
243
  typeof obj.source === "string");
228
244
  }
229
- // ── Utilities ───────────────────────────────────────────────────────────────
230
- function isExpired(mtimeMs, ttlMs) {
231
- return Date.now() - mtimeMs > ttlMs;
232
- }
@@ -1,7 +1,5 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import { fetchWithRetry, jsonWithByteCap, toErrorMessage } from "../../core/common";
4
- import { getRegistryIndexCacheDir } from "../../core/paths";
2
+ import { closeDatabase, getRegistryIndexCache, openDatabase, upsertRegistryIndexCache } from "../../indexer/db";
5
3
  import { asString } from "../../integrations/github";
6
4
  import { registerProvider } from "../factory";
7
5
  // ── Constants ───────────────────────────────────────────────────────────────
@@ -114,13 +112,25 @@ function assetHitToPreview(hit) {
114
112
  registerProvider("static-index", (config) => new StaticIndexProvider(config));
115
113
  // ── Index loading with cache ────────────────────────────────────────────────
116
114
  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;
115
+ // ── Step 1: Try DB cache (index.db) ─────────────────────────────────────
116
+ let db;
117
+ let dbCacheResult;
118
+ try {
119
+ db = openDatabase();
120
+ dbCacheResult = getRegistryIndexCache(db, entry.url, CACHE_TTL_MS);
121
+ }
122
+ catch {
123
+ // index.db not available yet (pre-migration install or test env) — fall through
124
+ }
125
+ if (dbCacheResult) {
126
+ const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
127
+ if (index) {
128
+ if (db)
129
+ closeDatabase(db);
130
+ return index;
131
+ }
122
132
  }
123
- // Try to fetch fresh index
133
+ // ── Step 2: Fetch fresh index from remote ────────────────────────────────
124
134
  try {
125
135
  const response = await fetchWithRetry(entry.url, undefined, { timeout: 10_000 });
126
136
  if (!response.ok) {
@@ -131,54 +141,40 @@ async function loadIndex(entry) {
131
141
  const data = await jsonWithByteCap(response, 50 * 1024 * 1024);
132
142
  const index = parseRegistryIndex(data);
133
143
  if (index) {
134
- writeCachedIndex(cachePath, index);
144
+ // Write to DB cache (primary)
145
+ if (db) {
146
+ try {
147
+ const etag = response.headers.get("etag") ?? undefined;
148
+ const lastModified = response.headers.get("last-modified") ?? undefined;
149
+ upsertRegistryIndexCache(db, entry.url, JSON.stringify(index), { etag, lastModified });
150
+ }
151
+ catch {
152
+ /* best-effort */
153
+ }
154
+ closeDatabase(db);
155
+ }
135
156
  return index;
136
157
  }
137
158
  throw new Error("Invalid registry index format");
138
159
  }
139
160
  catch (err) {
140
- // Fetch failed — use stale cache if available
141
- if (cached && !isCacheStale(cached.mtime)) {
142
- return cached.index;
161
+ if (db) {
162
+ try {
163
+ closeDatabase(db);
164
+ }
165
+ catch {
166
+ /* ignore */
167
+ }
168
+ }
169
+ // Fetch failed — use stale DB cache if available
170
+ if (dbCacheResult) {
171
+ const index = parseRegistryIndex(JSON.parse(dbCacheResult.indexJson));
172
+ if (index)
173
+ return index;
143
174
  }
144
175
  throw err;
145
176
  }
146
177
  }
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
178
  export function isCacheExpired(mtimeMs) {
183
179
  return Date.now() - mtimeMs > CACHE_TTL_MS;
184
180
  }
@@ -340,6 +340,7 @@ async function resolveNpmArtifact(parsed) {
340
340
  }
341
341
  async function resolveGithubArtifact(parsed) {
342
342
  const gitUrl = `https://github.com/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}.git`;
343
+ const repoBase = `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`;
343
344
  // Prefer git-backed installs so private GitHub repos work with the user's
344
345
  // normal git credential helper rather than requiring API-specific auth.
345
346
  const gitResolvedRevision = resolveGitRevisionFromRemote(gitUrl, parsed.requestedRef);
@@ -355,18 +356,18 @@ async function resolveGithubArtifact(parsed) {
355
356
  }
356
357
  const headers = githubHeaders();
357
358
  if (parsed.requestedRef) {
358
- const commit = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(parsed.requestedRef)}`, headers);
359
+ const commit = await tryFetchJson(`${repoBase}/commits/${encodeURIComponent(parsed.requestedRef)}`, headers);
359
360
  const resolvedRevision = asString(commit?.sha) ?? parsed.requestedRef;
360
361
  return {
361
362
  id: parsed.id,
362
363
  source: parsed.source,
363
364
  ref: parsed.ref,
364
- artifactUrl: `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/tarball/${encodeURIComponent(parsed.requestedRef)}`,
365
+ artifactUrl: `${repoBase}/tarball/${encodeURIComponent(parsed.requestedRef)}`,
365
366
  resolvedRevision,
366
367
  resolvedVersion: parsed.requestedRef,
367
368
  };
368
369
  }
369
- const latestRelease = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/releases/latest`, headers);
370
+ const latestRelease = await tryFetchJson(`${repoBase}/releases/latest`, headers);
370
371
  if (latestRelease) {
371
372
  const tarballUrl = asString(latestRelease.tarball_url);
372
373
  if (tarballUrl) {
@@ -380,17 +381,17 @@ async function resolveGithubArtifact(parsed) {
380
381
  };
381
382
  }
382
383
  }
383
- const repoMeta = await fetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`, headers);
384
+ const repoMeta = await fetchJson(repoBase, headers);
384
385
  const defaultBranch = asString(repoMeta.default_branch);
385
386
  if (!defaultBranch) {
386
387
  throw new Error(`Unable to resolve default branch for ${parsed.owner}/${parsed.repo}.`);
387
388
  }
388
- const commit = await tryFetchJson(`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(defaultBranch)}`, headers);
389
+ const commit = await tryFetchJson(`${repoBase}/commits/${encodeURIComponent(defaultBranch)}`, headers);
389
390
  return {
390
391
  id: parsed.id,
391
392
  source: parsed.source,
392
393
  ref: parsed.ref,
393
- artifactUrl: `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/tarball/${encodeURIComponent(defaultBranch)}`,
394
+ artifactUrl: `${repoBase}/tarball/${encodeURIComponent(defaultBranch)}`,
394
395
  resolvedVersion: defaultBranch,
395
396
  resolvedRevision: asString(commit?.sha) ?? defaultBranch,
396
397
  };
@@ -407,16 +408,7 @@ function resolveGitRevisionFromRemote(url, requestedRef) {
407
408
  return firstLine?.split(/\s/)[0] || undefined;
408
409
  }
409
410
  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
- }
411
+ const resolvedRevision = resolveGitRevisionFromRemote(parsed.url, parsed.requestedRef);
420
412
  return {
421
413
  id: parsed.id,
422
414
  source: parsed.source,