@agentxm/extension-discovery 0.28.4-bootstrap.0

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 (72) hide show
  1. package/LICENSE +110 -0
  2. package/dist/src/discover.d.ts +34 -0
  3. package/dist/src/discover.js +133 -0
  4. package/dist/src/index.d.ts +13 -0
  5. package/dist/src/index.js +12 -0
  6. package/dist/src/internal/environment.d.ts +14 -0
  7. package/dist/src/internal/environment.js +15 -0
  8. package/dist/src/packaging/bazel.d.ts +27 -0
  9. package/dist/src/packaging/bazel.js +125 -0
  10. package/dist/src/packaging/cargo.d.ts +32 -0
  11. package/dist/src/packaging/cargo.js +270 -0
  12. package/dist/src/packaging/cocoapods.d.ts +26 -0
  13. package/dist/src/packaging/cocoapods.js +187 -0
  14. package/dist/src/packaging/composer.d.ts +26 -0
  15. package/dist/src/packaging/composer.js +144 -0
  16. package/dist/src/packaging/conan.d.ts +30 -0
  17. package/dist/src/packaging/conan.js +238 -0
  18. package/dist/src/packaging/conda.d.ts +26 -0
  19. package/dist/src/packaging/conda.js +320 -0
  20. package/dist/src/packaging/cpan.d.ts +29 -0
  21. package/dist/src/packaging/cpan.js +189 -0
  22. package/dist/src/packaging/cran.d.ts +29 -0
  23. package/dist/src/packaging/cran.js +176 -0
  24. package/dist/src/packaging/detect.d.ts +16 -0
  25. package/dist/src/packaging/detect.js +25 -0
  26. package/dist/src/packaging/detected-package.d.ts +14 -0
  27. package/dist/src/packaging/detected-package.js +20 -0
  28. package/dist/src/packaging/docker.d.ts +27 -0
  29. package/dist/src/packaging/docker.js +256 -0
  30. package/dist/src/packaging/gem.d.ts +26 -0
  31. package/dist/src/packaging/gem.js +234 -0
  32. package/dist/src/packaging/golang.d.ts +26 -0
  33. package/dist/src/packaging/golang.js +169 -0
  34. package/dist/src/packaging/hackage.d.ts +26 -0
  35. package/dist/src/packaging/hackage.js +305 -0
  36. package/dist/src/packaging/hex.d.ts +28 -0
  37. package/dist/src/packaging/hex.js +186 -0
  38. package/dist/src/packaging/huggingface.d.ts +20 -0
  39. package/dist/src/packaging/huggingface.js +112 -0
  40. package/dist/src/packaging/index.d.ts +44 -0
  41. package/dist/src/packaging/index.js +120 -0
  42. package/dist/src/packaging/jsr.d.ts +30 -0
  43. package/dist/src/packaging/jsr.js +227 -0
  44. package/dist/src/packaging/julia.d.ts +27 -0
  45. package/dist/src/packaging/julia.js +165 -0
  46. package/dist/src/packaging/luarocks.d.ts +28 -0
  47. package/dist/src/packaging/luarocks.js +159 -0
  48. package/dist/src/packaging/maven.d.ts +27 -0
  49. package/dist/src/packaging/maven.js +471 -0
  50. package/dist/src/packaging/mojo.d.ts +29 -0
  51. package/dist/src/packaging/mojo.js +148 -0
  52. package/dist/src/packaging/npm.d.ts +26 -0
  53. package/dist/src/packaging/npm.js +190 -0
  54. package/dist/src/packaging/nuget.d.ts +26 -0
  55. package/dist/src/packaging/nuget.js +240 -0
  56. package/dist/src/packaging/opam.d.ts +27 -0
  57. package/dist/src/packaging/opam.js +287 -0
  58. package/dist/src/packaging/pub.d.ts +26 -0
  59. package/dist/src/packaging/pub.js +357 -0
  60. package/dist/src/packaging/pypi.d.ts +23 -0
  61. package/dist/src/packaging/pypi.js +448 -0
  62. package/dist/src/packaging/read.d.ts +20 -0
  63. package/dist/src/packaging/read.js +31 -0
  64. package/dist/src/packaging/reader-io.d.ts +29 -0
  65. package/dist/src/packaging/reader-io.js +29 -0
  66. package/dist/src/packaging/swift.d.ts +26 -0
  67. package/dist/src/packaging/swift.js +141 -0
  68. package/dist/src/packaging/types.d.ts +43 -0
  69. package/dist/src/packaging/types.js +8 -0
  70. package/dist/src/packaging/zig.d.ts +32 -0
  71. package/dist/src/packaging/zig.js +151 -0
  72. package/package.json +54 -0
@@ -0,0 +1,448 @@
1
+ /**
2
+ * pypi package detector and reader for package-compatibility discovery.
3
+ *
4
+ * Parses Python dependency files (pyproject.toml, requirements.txt,
5
+ * setup.cfg, Pipfile) and reads axm metadata from installed packages.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ * @packageDocumentation
9
+ */
10
+ import * as Effect from "effect/Effect";
11
+ import * as FileSystem from "effect/FileSystem";
12
+ import * as Option from "effect/Option";
13
+ import * as Path from "effect/Path";
14
+ import * as Result from "effect/Result";
15
+ import * as Schema from "effect/Schema";
16
+ import { extractTomlQuotedStrings } from "@agentxm/extension-workspace";
17
+ import { readEnv } from "../internal/environment.js";
18
+ import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
19
+ import { decodeAxmMeta, parseJsonOptional, readFileOptional } from "./reader-io.js";
20
+ const pypiType = Schema.decodeUnknownSync(PackageTypeSchema)("pypi");
21
+ // ---------------------------------------------------------------------------
22
+ // Name normalization
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Normalize a Python package name per purl spec:
26
+ * lowercase, replace underscores/dots/runs-of-dashes with single dash.
27
+ */
28
+ const normalizeName = (name) => name.toLowerCase().replace(/[-_.]+/g, "-");
29
+ // ---------------------------------------------------------------------------
30
+ // Version extraction
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Parse a PEP 440 version specifier string and return the version
34
+ * only if it is an exact pin without wildcards.
35
+ */
36
+ const extractExactVersion = (specifier) => {
37
+ const trimmed = specifier.trim();
38
+ if (trimmed === "")
39
+ return undefined;
40
+ // Only exact pins: ==X.Y.Z (no wildcard)
41
+ const exactMatch = /^==([^*,!~<>=]+)$/.exec(trimmed);
42
+ if (exactMatch?.[1] !== undefined)
43
+ return exactMatch[1].trim();
44
+ return undefined;
45
+ };
46
+ /**
47
+ * Parse a dependency line into name and optional version.
48
+ * Handles: `name`, `name[extras]`, `name>=1.0`, `name==1.0.0`, etc.
49
+ */
50
+ const parseDependencyLine = (line) => {
51
+ const trimmed = line.trim();
52
+ if (trimmed === "" || trimmed.startsWith("#"))
53
+ return undefined;
54
+ // Match package name (possibly with extras), then optional version specifiers
55
+ // Package names: letters, digits, hyphens, underscores, dots
56
+ const match = /^([A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9]|[A-Za-z0-9])(?:\[.*?\])?\s*(.*)$/.exec(trimmed);
57
+ if (!match)
58
+ return undefined;
59
+ const rawName = match[1] ?? "";
60
+ const versionPart = match[2]?.trim().replace(/;.*$/, "").trim() ?? ""; // strip environment markers
61
+ return {
62
+ name: normalizeName(rawName),
63
+ version: extractExactVersion(versionPart),
64
+ };
65
+ };
66
+ // ---------------------------------------------------------------------------
67
+ // File parsers
68
+ // ---------------------------------------------------------------------------
69
+ /**
70
+ * Parse pyproject.toml for dependencies.
71
+ * Uses simple regex parsing for the subset of TOML we need.
72
+ * Returns an Effect so we can log warnings for malformed content.
73
+ */
74
+ const parsePyprojectToml = (content, source) => Effect.gen(function* () {
75
+ const result = yield* Effect.try({
76
+ try: () => {
77
+ const packages = [];
78
+ // Parse [project] dependencies = [...]
79
+ const projectDepsMatch = /\[project\]\s*\n(?:(?!\[).*\n)*?dependencies\s*=\s*\[([\s\S]*?)\]/m.exec(content);
80
+ if (projectDepsMatch) {
81
+ const depsStr = projectDepsMatch[1] ?? "";
82
+ for (const dep of extractTomlQuotedStrings(depsStr)) {
83
+ const parsed = parseDependencyLine(dep);
84
+ if (parsed) {
85
+ packages.push(makeDetectedPackage(parsed.name, parsed.version, source));
86
+ }
87
+ }
88
+ }
89
+ // Parse [project.optional-dependencies] groups
90
+ const optDepsRegex = /\[project\.optional-dependencies\]\s*\n([\s\S]*?)(?=\n\[|\n*$)/gm;
91
+ let optMatch;
92
+ while ((optMatch = optDepsRegex.exec(content)) !== null) {
93
+ const section = optMatch[1] ?? "";
94
+ // Match group_name = [...]
95
+ const groupRegex = /\w+\s*=\s*\[([\s\S]*?)\]/gm;
96
+ let groupMatch;
97
+ while ((groupMatch = groupRegex.exec(section)) !== null) {
98
+ for (const dep of extractTomlQuotedStrings(groupMatch[1] ?? "")) {
99
+ const parsed = parseDependencyLine(dep);
100
+ if (parsed) {
101
+ packages.push(makeDetectedPackage(parsed.name, parsed.version, source));
102
+ }
103
+ }
104
+ }
105
+ }
106
+ return packages;
107
+ },
108
+ catch: () => ({ _tag: "PyprojectParseError" }),
109
+ }).pipe(Effect.option);
110
+ if (Option.isNone(result)) {
111
+ yield* Effect.logWarning("Malformed pyproject.toml, skipping");
112
+ const empty = [];
113
+ return empty;
114
+ }
115
+ return result.value;
116
+ });
117
+ /**
118
+ * Parse requirements.txt, following -r includes.
119
+ */
120
+ const parseRequirementsTxt = (projectDir, content, source, visited) => Effect.gen(function* () {
121
+ const path = yield* Path.Path;
122
+ const packages = [];
123
+ const lines = content.split("\n");
124
+ for (const line of lines) {
125
+ const trimmed = line.trim();
126
+ // Skip comments and blank lines
127
+ if (trimmed === "" || trimmed.startsWith("#"))
128
+ continue;
129
+ // Handle -r include directives
130
+ const includeMatch = /^-r\s+(.+)$/.exec(trimmed);
131
+ if (includeMatch) {
132
+ const includeFile = (includeMatch[1] ?? "").trim();
133
+ const includePath = path.join(projectDir, includeFile);
134
+ if (!visited.has(includePath)) {
135
+ visited.add(includePath);
136
+ const includeContent = yield* readFileOptional(includePath);
137
+ if (Option.isSome(includeContent)) {
138
+ const included = yield* parseRequirementsTxt(projectDir, includeContent.value, includeFile, visited);
139
+ packages.push(...included);
140
+ }
141
+ else {
142
+ yield* Effect.logWarning(`requirements.txt: -r target not found: ${includeFile}`);
143
+ }
144
+ }
145
+ continue;
146
+ }
147
+ // Skip other pip options (-e, -i, --index-url, etc.)
148
+ if (trimmed.startsWith("-"))
149
+ continue;
150
+ const parsed = parseDependencyLine(trimmed);
151
+ if (parsed) {
152
+ packages.push(makeDetectedPackage(parsed.name, parsed.version, source));
153
+ }
154
+ }
155
+ return packages;
156
+ });
157
+ /**
158
+ * Parse setup.cfg [options] install_requires.
159
+ * INI format: section headers in brackets, continuation lines are indented.
160
+ */
161
+ const parseSetupCfg = (content, source) => {
162
+ const packages = [];
163
+ const lines = content.split("\n");
164
+ let inOptions = false;
165
+ let inInstallRequires = false;
166
+ for (const line of lines) {
167
+ const trimmed = line.trim();
168
+ // Section headers
169
+ if (trimmed.startsWith("[")) {
170
+ inOptions = trimmed === "[options]";
171
+ inInstallRequires = false;
172
+ continue;
173
+ }
174
+ if (!inOptions)
175
+ continue;
176
+ // Check for install_requires key
177
+ if (/^install_requires\s*=/.test(trimmed)) {
178
+ inInstallRequires = true;
179
+ // Value may be on the same line after =
180
+ const afterEq = trimmed.replace(/^install_requires\s*=\s*/, "").trim();
181
+ if (afterEq !== "") {
182
+ const parsed = parseDependencyLine(afterEq);
183
+ if (parsed) {
184
+ packages.push(makeDetectedPackage(parsed.name, parsed.version, source));
185
+ }
186
+ }
187
+ continue;
188
+ }
189
+ // Continuation lines (indented) under install_requires
190
+ if (inInstallRequires) {
191
+ // Non-indented non-empty line means we've left the value
192
+ if (trimmed !== "" && !line.startsWith(" ") && !line.startsWith("\t")) {
193
+ inInstallRequires = false;
194
+ continue;
195
+ }
196
+ if (trimmed === "" || trimmed.startsWith("#"))
197
+ continue;
198
+ const parsed = parseDependencyLine(trimmed);
199
+ if (parsed) {
200
+ packages.push(makeDetectedPackage(parsed.name, parsed.version, source));
201
+ }
202
+ }
203
+ }
204
+ return packages;
205
+ };
206
+ /**
207
+ * Parse Pipfile [packages] section.
208
+ */
209
+ const parsePipfile = (content, source) => {
210
+ const packages = [];
211
+ // Find [packages] section start and extract lines until next section header
212
+ const packagesIdx = content.indexOf("[packages]");
213
+ if (packagesIdx === -1)
214
+ return packages;
215
+ const afterHeader = content.slice(packagesIdx + "[packages]".length);
216
+ // Next section starts with \n[ at the beginning of a line
217
+ const nextSectionIdx = afterHeader.search(/\n\[/);
218
+ const sectionContent = nextSectionIdx >= 0 ? afterHeader.slice(0, nextSectionIdx) : afterHeader;
219
+ const lines = sectionContent.split("\n");
220
+ for (const line of lines) {
221
+ const trimmed = line.trim();
222
+ if (trimmed === "" || trimmed.startsWith("#"))
223
+ continue;
224
+ // Pipfile format: name = "version_spec" or name = "*" or name = {version = ">=1.0"}
225
+ const kvMatch = /^([A-Za-z0-9][-A-Za-z0-9_.]*)\s*=\s*(.*)$/.exec(trimmed);
226
+ if (!kvMatch)
227
+ continue;
228
+ const rawName = kvMatch[1] ?? "";
229
+ const valuePart = (kvMatch[2] ?? "").trim();
230
+ let version;
231
+ // Simple string value: ">=4.0" or "*"
232
+ const strMatch = /^["']([^"']*)["']$/.exec(valuePart);
233
+ if (strMatch) {
234
+ const spec = strMatch[1] ?? "";
235
+ if (spec !== "*") {
236
+ version = extractExactVersion(spec);
237
+ }
238
+ }
239
+ packages.push(makeDetectedPackage(normalizeName(rawName), version, source));
240
+ }
241
+ return packages;
242
+ };
243
+ // ---------------------------------------------------------------------------
244
+ // Helpers
245
+ // ---------------------------------------------------------------------------
246
+ const makeDetectedPackage = (name, version, source) => ({
247
+ purl: {
248
+ type: pypiType,
249
+ name,
250
+ ...(version !== undefined ? { version } : {}),
251
+ },
252
+ type: pypiType,
253
+ source,
254
+ });
255
+ // ---------------------------------------------------------------------------
256
+ // Detector
257
+ // ---------------------------------------------------------------------------
258
+ /**
259
+ * pypi package detector. Parses Python dependency files in priority order.
260
+ *
261
+ * @experimental This API is unstable and may change without notice.
262
+ */
263
+ export const pypiDetector = {
264
+ type: pypiType,
265
+ detect: Effect.fn("detect.pypi")(function* (projectDir) {
266
+ const path = yield* Path.Path;
267
+ const allPackages = [];
268
+ const seenNames = new Set();
269
+ const addUnique = (pkgs) => {
270
+ for (const pkg of pkgs) {
271
+ if (!seenNames.has(pkg.purl.name)) {
272
+ seenNames.add(pkg.purl.name);
273
+ allPackages.push(pkg);
274
+ }
275
+ }
276
+ };
277
+ // 1. pyproject.toml
278
+ const pyprojectPath = path.join(projectDir, "pyproject.toml");
279
+ const pyprojectContent = yield* readFileOptional(pyprojectPath);
280
+ if (Option.isSome(pyprojectContent)) {
281
+ const deps = yield* parsePyprojectToml(pyprojectContent.value, "pyproject.toml");
282
+ addUnique(deps);
283
+ }
284
+ // 2. requirements.txt
285
+ const reqPath = path.join(projectDir, "requirements.txt");
286
+ const reqContent = yield* readFileOptional(reqPath);
287
+ if (Option.isSome(reqContent)) {
288
+ const reqPackages = yield* parseRequirementsTxt(projectDir, reqContent.value, "requirements.txt", new Set([reqPath]));
289
+ addUnique(reqPackages);
290
+ }
291
+ // 3. setup.cfg
292
+ const setupCfgPath = path.join(projectDir, "setup.cfg");
293
+ const setupCfgContent = yield* readFileOptional(setupCfgPath);
294
+ if (Option.isSome(setupCfgContent)) {
295
+ addUnique(parseSetupCfg(setupCfgContent.value, "setup.cfg"));
296
+ }
297
+ // 4. Pipfile
298
+ const pipfilePath = path.join(projectDir, "Pipfile");
299
+ const pipfileContent = yield* readFileOptional(pipfilePath);
300
+ if (Option.isSome(pipfileContent)) {
301
+ addUnique(parsePipfile(pipfileContent.value, "Pipfile"));
302
+ }
303
+ return allPackages;
304
+ }, Effect.annotateLogs({ detector: "pypi" })),
305
+ };
306
+ // ---------------------------------------------------------------------------
307
+ // Reader helpers
308
+ // ---------------------------------------------------------------------------
309
+ /**
310
+ * Normalize a dist-info directory name for comparison:
311
+ * lowercase, replace underscores/dots/dashes uniformly.
312
+ */
313
+ const normalizeDistInfoName = (name) => name.toLowerCase().replace(/[-_.]+/g, "-");
314
+ /**
315
+ * Parse an INI-style entry_points.txt and check for [axm] group.
316
+ * Returns the first value from the [axm] group if present.
317
+ */
318
+ const parseEntryPoints = (content) => {
319
+ const lines = content.split("\n");
320
+ let inAxmGroup = false;
321
+ for (const line of lines) {
322
+ const trimmed = line.trim();
323
+ // Section header
324
+ const sectionMatch = /^\[(.+)\]$/.exec(trimmed);
325
+ if (sectionMatch) {
326
+ inAxmGroup = sectionMatch[1] === "axm";
327
+ continue;
328
+ }
329
+ if (inAxmGroup && trimmed !== "" && !trimmed.startsWith("#")) {
330
+ // key = value format
331
+ const kvMatch = /^(\S+)\s*=\s*(.+)$/.exec(trimmed);
332
+ if (kvMatch) {
333
+ return Option.some((kvMatch[2] ?? "").trim());
334
+ }
335
+ }
336
+ }
337
+ return Option.none();
338
+ };
339
+ /**
340
+ * Resolve the site-packages directory to scan.
341
+ * Checks $VIRTUAL_ENV first, then falls back to system.
342
+ */
343
+ const resolveSitePackages = () => Effect.gen(function* () {
344
+ const fs = yield* FileSystem.FileSystem;
345
+ const path = yield* Path.Path;
346
+ const virtualEnv = yield* Effect.sync(() => readEnv("VIRTUAL_ENV"));
347
+ if (virtualEnv) {
348
+ // Scan for python* directories under lib/
349
+ const libDir = path.join(virtualEnv, "lib");
350
+ const libExists = yield* fs.exists(libDir).pipe(Effect.catch(() => Effect.succeed(false)));
351
+ if (libExists) {
352
+ const entries = yield* fs.readDirectory(libDir).pipe(Effect.catch(() => {
353
+ const empty = [];
354
+ return Effect.succeed(empty);
355
+ }));
356
+ for (const entry of entries) {
357
+ if (entry.startsWith("python")) {
358
+ const sp = path.join(libDir, entry, "site-packages");
359
+ const spExists = yield* fs.exists(sp).pipe(Effect.catch(() => Effect.succeed(false)));
360
+ if (spExists)
361
+ return Option.some(sp);
362
+ }
363
+ }
364
+ }
365
+ }
366
+ // Fallback: no site-packages found
367
+ return Option.none();
368
+ });
369
+ /**
370
+ * Find a .dist-info directory matching the given package name.
371
+ */
372
+ const findDistInfo = (sitePackages, packageName) => Effect.gen(function* () {
373
+ const fs = yield* FileSystem.FileSystem;
374
+ const path = yield* Path.Path;
375
+ const normalizedTarget = normalizeDistInfoName(packageName);
376
+ const entries = yield* fs.readDirectory(sitePackages).pipe(Effect.catch(() => {
377
+ const empty = [];
378
+ return Effect.succeed(empty);
379
+ }));
380
+ for (const entry of entries) {
381
+ if (!entry.endsWith(".dist-info"))
382
+ continue;
383
+ // dist-info format: Name-Version.dist-info
384
+ // Extract the name part (everything before the last hyphen-version)
385
+ const withoutSuffix = entry.replace(/\.dist-info$/, "");
386
+ // Split on hyphen that precedes a version (digit)
387
+ const dashIdx = withoutSuffix.search(/-\d/);
388
+ const dirName = dashIdx >= 0 ? withoutSuffix.substring(0, dashIdx) : withoutSuffix;
389
+ if (normalizeDistInfoName(dirName) === normalizedTarget) {
390
+ return Option.some(path.join(sitePackages, entry));
391
+ }
392
+ }
393
+ return Option.none();
394
+ });
395
+ // ---------------------------------------------------------------------------
396
+ // Reader
397
+ // ---------------------------------------------------------------------------
398
+ /**
399
+ * pypi package reader. Reads axm metadata from installed Python packages.
400
+ *
401
+ * @experimental This API is unstable and may change without notice.
402
+ */
403
+ export const pypiReader = {
404
+ type: pypiType,
405
+ read: Effect.fn("read.pypi")(function* (pkg) {
406
+ const path = yield* Path.Path;
407
+ // 1. Resolve site-packages
408
+ const sitePackagesOpt = yield* resolveSitePackages();
409
+ if (Option.isNone(sitePackagesOpt))
410
+ return Option.none();
411
+ const sitePackages = sitePackagesOpt.value;
412
+ // 2. Find .dist-info directory
413
+ const distInfoOpt = yield* findDistInfo(sitePackages, pkg.purl.name);
414
+ if (Option.isNone(distInfoOpt))
415
+ return Option.none();
416
+ const distInfoDir = distInfoOpt.value;
417
+ // 3. Read entry_points.txt
418
+ const entryPointsPath = path.join(distInfoDir, "entry_points.txt");
419
+ const entryPointsContent = yield* readFileOptional(entryPointsPath);
420
+ if (Option.isNone(entryPointsContent))
421
+ return Option.none();
422
+ // 4. Check for [axm] group
423
+ const axmEntry = parseEntryPoints(entryPointsContent.value);
424
+ if (Option.isNone(axmEntry))
425
+ return Option.none();
426
+ // 5. Locate axm.json from the entry point value
427
+ // Entry format: "package_module:axm.json" -> look for axm.json in the package dir
428
+ const entryValue = axmEntry.value;
429
+ const colonIdx = entryValue.indexOf(":");
430
+ const modulePart = colonIdx >= 0 ? entryValue.substring(0, colonIdx) : entryValue;
431
+ const filePart = colonIdx >= 0 ? entryValue.substring(colonIdx + 1) : "axm.json";
432
+ const axmJsonPath = path.join(sitePackages, modulePart, filePart);
433
+ const axmJsonContent = yield* readFileOptional(axmJsonPath);
434
+ if (Option.isNone(axmJsonContent))
435
+ return Option.none();
436
+ // 6. Parse and validate axm.json
437
+ const parsed = yield* parseJsonOptional(axmJsonContent.value, `${pkg.purl.name}/axm.json`);
438
+ if (Option.isNone(parsed))
439
+ return Option.none();
440
+ const metaResult = decodeAxmMeta(parsed.value);
441
+ if (Result.isFailure(metaResult)) {
442
+ yield* Effect.logWarning(`Invalid axm metadata in ${pkg.purl.name}: schema validation failed`);
443
+ return Option.none();
444
+ }
445
+ return Option.some(metaResult.success.extensions);
446
+ }, Effect.annotateLogs({ reader: "pypi" })),
447
+ };
448
+ //# sourceMappingURL=pypi.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Read orchestrator: matches detected packages to readers and collects
3
+ * recommended extension refs into a HashMap keyed by encoded purl.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ * @packageDocumentation
7
+ */
8
+ import * as Effect from "effect/Effect";
9
+ import * as HashMap from "effect/HashMap";
10
+ import type { DetectedPackage, PackageReader } from "./types.js";
11
+ /**
12
+ * Read local recommendations for each detected package using matching readers.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ export declare const readLocalRecommendations: (packages: readonly DetectedPackage[], readers: readonly PackageReader[]) => Effect.Effect<HashMap.HashMap<string, readonly {
17
+ readonly ref: string;
18
+ readonly versionRange?: (string & import("effect/Brand").Brand<"VersionRange">) | null | undefined;
19
+ }[]>, never, import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
20
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Read orchestrator: matches detected packages to readers and collects
3
+ * recommended extension refs into a HashMap keyed by encoded purl.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ * @packageDocumentation
7
+ */
8
+ import * as Array from "effect/Array";
9
+ import * as Effect from "effect/Effect";
10
+ import * as HashMap from "effect/HashMap";
11
+ import * as Option from "effect/Option";
12
+ import * as Schema from "effect/Schema";
13
+ import { PackageUrlSchema } from "@agentxm/extension-model/unstable/packaging/package-url";
14
+ const encodePurl = Schema.encodeSync(PackageUrlSchema);
15
+ /**
16
+ * Read local recommendations for each detected package using matching readers.
17
+ *
18
+ * @experimental This API is unstable and may change without notice.
19
+ */
20
+ export const readLocalRecommendations = Effect.fn("discover.readLocal")(function* (packages, readers) {
21
+ const results = yield* Effect.forEach(packages, (pkg) => {
22
+ const reader = readers.find((r) => r.type === pkg.type);
23
+ if (!reader)
24
+ return Effect.succeed(Option.none());
25
+ return reader
26
+ .read(pkg)
27
+ .pipe(Effect.map((result) => Option.map(result, (refs) => [encodePurl(pkg.purl), refs])));
28
+ }, { concurrency: "unbounded" });
29
+ return HashMap.fromIterable(Array.getSomes(results));
30
+ });
31
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared reader I/O helpers for package-compatibility discovery.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as FileSystem from "effect/FileSystem";
8
+ import * as Option from "effect/Option";
9
+ import * as Schema from "effect/Schema";
10
+ export declare const decodePurl: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => {
11
+ readonly type: string & import("effect/Brand").Brand<"PackageType">;
12
+ readonly name: string;
13
+ readonly namespace?: string | undefined;
14
+ readonly version?: string | undefined;
15
+ readonly qualifiers?: {
16
+ readonly [x: string]: string;
17
+ } | undefined;
18
+ readonly subpath?: string | undefined;
19
+ };
20
+ export declare const decodeAxmMeta: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => import("effect/Result").Result<{
21
+ readonly extensions: readonly {
22
+ readonly ref: string;
23
+ readonly versionRange?: (string & import("effect/Brand").Brand<"VersionRange">) | null | undefined;
24
+ }[];
25
+ readonly $schema?: string | undefined;
26
+ }, Schema.SchemaError>;
27
+ export declare const readFileOptional: (filePath: string) => Effect.Effect<Option.Option<string>, never, FileSystem.FileSystem>;
28
+ export declare const parseJsonOptional: (content: string, context: string) => Effect.Effect<Option.Option<unknown>, never, never>;
29
+ //# sourceMappingURL=reader-io.d.ts.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared reader I/O helpers for package-compatibility discovery.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as FileSystem from "effect/FileSystem";
8
+ import * as Option from "effect/Option";
9
+ import * as Schema from "effect/Schema";
10
+ import { AxmPackageMetaSchema } from "@agentxm/registry-client";
11
+ import { PackageUrlSchema } from "@agentxm/extension-model/unstable/packaging/package-url";
12
+ export const decodePurl = Schema.decodeUnknownSync(PackageUrlSchema);
13
+ export const decodeAxmMeta = Schema.decodeUnknownResult(AxmPackageMetaSchema);
14
+ export const readFileOptional = (filePath) => Effect.gen(function* () {
15
+ const fs = yield* FileSystem.FileSystem;
16
+ return yield* fs.readFileString(filePath).pipe(Effect.option);
17
+ });
18
+ export const parseJsonOptional = (content, context) => Effect.gen(function* () {
19
+ const result = yield* Effect.try({
20
+ try: () => JSON.parse(content),
21
+ catch: () => ({ _tag: "JsonParseError" }),
22
+ }).pipe(Effect.option);
23
+ if (Option.isNone(result)) {
24
+ yield* Effect.logWarning(`Malformed JSON in ${context}, skipping`);
25
+ return Option.none();
26
+ }
27
+ return Option.some(result.value);
28
+ });
29
+ //# sourceMappingURL=reader-io.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Swift package detector and reader for package-compatibility discovery.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ * @packageDocumentation
6
+ */
7
+ import type { PackageDetector, PackageReader } from "./types.js";
8
+ /**
9
+ * Swift package detector.
10
+ *
11
+ * Scans `Package.swift` in the project directory and extracts `.package(url: ...)`
12
+ * dependencies using regex pattern matching.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ export declare const swiftDetector: PackageDetector;
17
+ /**
18
+ * Swift package reader.
19
+ *
20
+ * Reads `.build/checkouts/<package-name>/axm.json` for each detected SwiftPM
21
+ * package and extracts recommendation metadata.
22
+ *
23
+ * @experimental This API is unstable and may change without notice.
24
+ */
25
+ export declare const swiftReader: PackageReader;
26
+ //# sourceMappingURL=swift.d.ts.map