@rse/ase 0.9.35 → 0.9.36

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/dst/ase-skills.js CHANGED
@@ -37,17 +37,19 @@ export class Skills {
37
37
  const time = pkg.time ?? {};
38
38
  const verEntry = version !== "" ? pkg.versions?.[version] : undefined;
39
39
  let repository = "";
40
+ let deps = "N.A.";
40
41
  if (verEntry !== undefined) {
41
42
  const r = verEntry.repository;
42
43
  if (typeof r === "string")
43
44
  repository = r;
44
45
  else if (r !== undefined && typeof r.url === "string")
45
46
  repository = r.url;
47
+ deps = Object.keys(verEntry.dependencies ?? {}).length;
46
48
  }
47
- return { version, time, repository };
49
+ return { version, time, repository, deps };
48
50
  }
49
51
  catch {
50
- return { version: "", time: {}, repository: "" };
52
+ return { version: "", time: {}, repository: "", deps: "N.A." };
51
53
  }
52
54
  }
53
55
  /* fetch GitHub stars given a repository URL (or empty string) */
@@ -83,7 +85,7 @@ export class Skills {
83
85
  static async fetchMavenInfo(coord) {
84
86
  const [groupId, artifactId] = coord.split(":", 2);
85
87
  if (groupId === undefined || artifactId === undefined || groupId === "" || artifactId === "")
86
- return { version: "", created: "", updated: "", repository: "" };
88
+ return { version: "", created: "", updated: "", repository: "", deps: "N.A." };
87
89
  try {
88
90
  const latest = await Skills.httpLimit(() => ofetch("https://search.maven.org/solrsearch/select" +
89
91
  `?q=g:%22${encodeURIComponent(groupId)}%22+AND+a:%22${encodeURIComponent(artifactId)}%22` +
@@ -113,6 +115,7 @@ export class Skills {
113
115
  }
114
116
  const created = typeof firstTs === "number" ? new Date(firstTs).toISOString() : updated;
115
117
  let repository = "";
118
+ let deps = "N.A.";
116
119
  if (version !== "") {
117
120
  try {
118
121
  const pom = await Skills.httpLimit(() => ofetch(`https://repo1.maven.org/maven2/${groupId.replace(/\./g, "/")}` +
@@ -126,16 +129,31 @@ export class Skills {
126
129
  if (url !== null)
127
130
  repository = url[1];
128
131
  }
132
+ /* count the `<dependency>` entries of the direct
133
+ `<dependencies>` block, skipping `test`/`provided`
134
+ scopes; a `<dependencyManagement>` block is ignored
135
+ as it only declares versions, not real dependencies */
136
+ const managed = /<dependencyManagement>[\s\S]*?<\/dependencyManagement>/gi;
137
+ const stripped = pom.replace(managed, "");
138
+ const depRe = /<dependency>([\s\S]*?)<\/dependency>/gi;
139
+ let count = 0;
140
+ let dep;
141
+ while ((dep = depRe.exec(stripped)) !== null) {
142
+ const scope = /<scope>\s*([^<\s]+)\s*<\/scope>/i.exec(dep[1]);
143
+ if (scope === null || (scope[1] !== "test" && scope[1] !== "provided"))
144
+ count++;
145
+ }
146
+ deps = count;
129
147
  }
130
148
  }
131
149
  catch {
132
150
  repository = "";
133
151
  }
134
152
  }
135
- return { version, created, updated, repository };
153
+ return { version, created, updated, repository, deps };
136
154
  }
137
155
  catch {
138
- return { version: "", created: "", updated: "", repository: "" };
156
+ return { version: "", created: "", updated: "", repository: "", deps: "N.A." };
139
157
  }
140
158
  }
141
159
  /* fetch the "Used By" count from `mvnrepository.com` as a downloads proxy
@@ -180,7 +198,7 @@ export class Skills {
180
198
  - "JavaScript"/"TypeScript": NPM registry (pacote) + GitHub stars + npm-downloads
181
199
  - "Java"/"Kotlin": Maven Central + GitHub stars + mvnrepository.com "Used By"
182
200
  - "Unknown": not supported -- return empty result */
183
- static async info(stack, components) {
201
+ static async info(stack, components, staleMonths = 18, smallScope = false) {
184
202
  if (stack === "JavaScript" || stack === "TypeScript") {
185
203
  /* per package: kick off packument and downloads in parallel,
186
204
  then stars as soon as the packument resolves; across packages
@@ -194,7 +212,7 @@ export class Skills {
194
212
  ]);
195
213
  const created = p.time.created ?? "";
196
214
  const updated = p.version !== "" ? (p.time[p.version] ?? "") : "";
197
- const rank = Skills.computeRank(downloads, stars, created, updated);
215
+ const rank = Skills.computeRank(downloads, stars, created, updated, p.deps, staleMonths, smallScope);
198
216
  return {
199
217
  name,
200
218
  version: p.version,
@@ -203,6 +221,7 @@ export class Skills {
203
221
  repository: p.repository,
204
222
  stars,
205
223
  downloads,
224
+ deps: p.deps,
206
225
  rank
207
226
  };
208
227
  }));
@@ -222,7 +241,7 @@ export class Skills {
222
241
  const [i, downloads, stars] = await Promise.all([
223
242
  infoPromise, downloadsPromise, starsPromise
224
243
  ]);
225
- const rank = Skills.computeRank(downloads, stars, i.created, i.updated);
244
+ const rank = Skills.computeRank(downloads, stars, i.created, i.updated, i.deps, staleMonths, smallScope);
226
245
  return {
227
246
  name,
228
247
  version: i.version,
@@ -231,6 +250,7 @@ export class Skills {
231
250
  repository: i.repository,
232
251
  stars,
233
252
  downloads,
253
+ deps: i.deps,
234
254
  rank
235
255
  };
236
256
  }));
@@ -253,7 +273,7 @@ export class Skills {
253
273
  is structurally unavailable, e.g. Maven Central exposes no
254
274
  per-artifact download counts) is likewise treated as neutral `1`, so
255
275
  such stacks can still be ranked by the remaining metrics. */
256
- static computeRank(downloads, stars, created, updated) {
276
+ static computeRank(downloads, stars, created, updated, deps, staleMonths, smallScope) {
257
277
  const d = typeof downloads === "number" ? downloads + 1 : 1;
258
278
  const s = typeof stars === "number" ? stars + 1 : 1;
259
279
  const cMs = created !== "" ? Date.parse(created) : NaN;
@@ -277,7 +297,31 @@ export class Skills {
277
297
  entry rankable without rewarding the missing date. */
278
298
  const lifespan = (!Number.isNaN(cMs) && !Number.isNaN(uMs)) ? Math.max(0, uMs - cMs) : 1;
279
299
  const recentness = !Number.isNaN(uMs) ? Math.exp(-Math.max(0, (now - uMs) / msPerDay) / halfLife) : 0.5;
280
- return d * s * lifespan * recentness;
300
+ let rank = d * s * lifespan * recentness;
301
+ /* hard, caller-tunable staleness penalty on top of the soft
302
+ `recentness` decay: unlike the smooth exp-decay above, this is a
303
+ two-tier *cliff* keyed off the `staleMonths` threshold. Past the
304
+ first tier (`staleMonths`) the rank is cut to `0.3x`, past the
305
+ second tier (`2 x staleMonths`, i.e. long abandoned) to `0.1x`.
306
+ A component below the first tier -- or one whose `updated` date
307
+ is unknown -- is left unpenalized. */
308
+ if (!Number.isNaN(uMs)) {
309
+ const ageMonths = (now - uMs) / msPerDay / 30;
310
+ if (ageMonths > 2 * staleMonths)
311
+ rank *= 0.1;
312
+ else if (ageMonths > staleMonths)
313
+ rank *= 0.3;
314
+ }
315
+ /* small-scope dependency-weight penalty: for a narrow, self-
316
+ contained need, dragging in a heavy dependency tree is usually
317
+ the wrong call, so each real component is demoted in proportion
318
+ to its own number of *direct* dependencies (`1 / (1 + deps)`).
319
+ A zero-dep component is left unpenalized; an unknown `deps` count
320
+ (`"N.A."`) is treated neutrally. This only applies when the
321
+ caller flags the need as small-scope. */
322
+ if (smallScope && typeof deps === "number")
323
+ rank *= 1 / (1 + deps);
324
+ return rank;
281
325
  }
282
326
  /* compute the per-alternative product-sum (rating) row from a
283
327
  weighted decision matrix. Each input row has the shape
@@ -318,20 +362,28 @@ export class SkillsMCP {
318
362
  "version + earliest/latest release timestamps from the Maven Central Solr " +
319
363
  "search endpoint, the SCM/project URL from the latest POM at `repo1.maven.org`, " +
320
364
  "GitHub stars (if applicable), and the \"Used By\" count from `mvnrepository.com` " +
321
- "as the `downloads` proxy. Returns a JSON `text` array of " +
322
- "`{ name, version, created, updated, repository, stars, downloads, rank }` " +
323
- "objects, sorted in descending order by `rank`. Failures of individual side " +
324
- "calls are isolated and reported as `\"N.A.\"` or empty string so every entry " +
325
- "has the full shape.",
365
+ "as the `downloads` proxy. The number of *direct* dependencies is read as `deps` " +
366
+ "(from the packument version entry for NPM, or the latest POM's `<dependencies>` " +
367
+ "block for Maven). Returns a JSON `text` array of " +
368
+ "`{ name, version, created, updated, repository, stars, downloads, deps, rank }` " +
369
+ "objects, sorted in descending order by `rank`. The `rank` applies a two-tier hard " +
370
+ "staleness penalty keyed off `staleMonths` (0.3x past the threshold, 0.1x past twice " +
371
+ "it) and, when `smallScope` is set, a dependency-weight penalty of `1/(1+deps)` so " +
372
+ "dependency-heavy components sink. Failures of individual side calls are isolated " +
373
+ "and reported as `\"N.A.\"` or empty string so every entry has the full shape.",
326
374
  inputSchema: {
327
375
  stack: z.string()
328
376
  .describe("Technology stack: \"JavaScript\", \"TypeScript\", \"Java\", \"Kotlin\", or \"Unknown\""),
329
377
  components: z.array(z.string())
330
- .describe("List of package names (NPM) or Maven coordinates `groupId:artifactId` (Java/Kotlin)")
378
+ .describe("List of package names (NPM) or Maven coordinates `groupId:artifactId` (Java/Kotlin)"),
379
+ staleMonths: z.number().optional()
380
+ .describe("Staleness threshold in months (default 18); a release older than this is rank-penalized"),
381
+ smallScope: z.boolean().optional()
382
+ .describe("If true, penalize each component by its direct-dependency count (small-scope need)")
331
383
  }
332
384
  }, async (args) => {
333
385
  try {
334
- const result = await Skills.info(args.stack, args.components);
386
+ const result = await Skills.info(args.stack, args.components, args.staleMonths, args.smallScope);
335
387
  return {
336
388
  content: [{ type: "text", text: JSON.stringify(result) }]
337
389
  };
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "homepage": "http://github.com/rse/ase",
7
7
  "repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
8
8
  "bugs": { "url": "http://github.com/rse/ase/issues" },
9
- "version": "0.9.35",
9
+ "version": "0.9.36",
10
10
  "license": "Apache-2.0",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.35",
3
+ "version": "0.9.36",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.35",
3
+ "version": "0.9.36",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.35",
3
+ "version": "0.9.36",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -6,7 +6,7 @@
6
6
  "homepage": "http://github.com/rse/ase",
7
7
  "repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
8
8
  "bugs": { "url": "http://github.com/rse/ase/issues" },
9
- "version": "0.9.35",
9
+ "version": "0.9.36",
10
10
  "license": "Apache-2.0",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ase-arch-discover
3
- argument-hint: "[--help|-h] [--limit|-l=12] <functionality>"
3
+ argument-hint: "[--help|-h] [--limit|-l=12] [--staleness|-s=18] [--small-scope|-S] <functionality>"
4
4
  description: >
5
5
  Discover additional, third-party components (libraries/frameworks) for
6
6
  the technology stack to provide needed functionality.
@@ -25,7 +25,7 @@ Discover Components
25
25
 
26
26
  <expand name="getopt"
27
27
  arg1="ase-arch-discover"
28
- arg2="--limit|-l=12">
28
+ arg2="--limit|-l=12 --staleness|-s=18 --small-scope|-S">
29
29
  $ARGUMENTS
30
30
  </expand>
31
31
 
@@ -166,18 +166,25 @@ for the technology stack to *provide* the *needed functionality*
166
166
  entries by Maven coordinate.
167
167
 
168
168
  6. Call the `ase_component_info(stack: "<stack/>", components:
169
- [ "<package-1/>", ..., "<package-C/>" ])` tool of the `ase` MCP
170
- server *once* for the entire candidate set of discovered packages.
171
- The tool dispatches internally on <stack/> and fetches all
172
- metadata in maximum parallel and returns an array of objects `{
173
- name, version, created, updated, repository, stars, downloads,
174
- rank }`. For each
175
- component <component-K/> (K=1-C) read from its corresponding
176
- entry: <version-K/> from `version`, <updated-K/> from `updated`,
177
- <created-K/> from `created`, <repository-K/> from `repository`,
178
- <stars-K/> from `stars` (numeric or `N.A.`), <downloads-K/>
179
- from `downloads` (numeric or `N.A.`) and <rank-K/> from `rank`
180
- (numeric).
169
+ [ "<package-1/>", ..., "<package-C/>" ], staleMonths:
170
+ <getopt-option-staleness/>, smallScope: <getopt-option-small-scope/>)`
171
+ tool of the `ase` MCP server *once* for the entire candidate set of
172
+ discovered packages. When the <getopt-option-small-scope/> option is
173
+ enabled, the ranking treats the <functionality/> as *small-scope*,
174
+ i.e. narrow, self-contained, and low-effort enough that a
175
+ *dependency-free/hand-rolled* implementation is a realistic
176
+ alternative to pulling in a third-party component. The tool
177
+ dispatches internally on <stack/>
178
+ and fetches all metadata in maximum parallel and returns an array
179
+ of objects `{ name, version, created, updated, repository, stars,
180
+ downloads, deps, rank }`. For each component <component-K/>
181
+ (K=1-C) read from its corresponding entry: <version-K/> from
182
+ `version`, <updated-K/> from `updated`, <created-K/> from
183
+ `created`, <repository-K/> from `repository`, <stars-K/> from
184
+ `stars` (numeric or `N.A.`), <downloads-K/> from `downloads`
185
+ (numeric or `N.A.`), <deps-K/> from `deps` (numeric or `N.A.`)
186
+ and <rank-K/> from `rank` (numeric). The returned `rank` already
187
+ reflects the staleness and small-scope dependency penalties.
181
188
 
182
189
  7. Sort, in descending order, the discovered candidate components
183
190
  <component-K/> (K=1-C) by their `rank` field and trim the result
@@ -192,7 +199,15 @@ for the technology stack to *provide* the *needed functionality*
192
199
  you should not stumble over) is its single most distinguishing
193
200
  perspective, and remember this as an <info-K/> (K=1-N) formatted
194
201
  like `<type/>: <hint/>` where <type/> is one of `USP`, `Crux`,
195
- or `Gotcha` and <hint/> is a 1-6 word hint. Do not output
202
+ or `Gotcha` and <hint/> is a 1-6 word hint.
203
+
204
+ *Staleness override*: determine the coarse age <age-K/> (like
205
+ `2y`) of the last release from <updated-K/>. If <updated-K/> is
206
+ known and its age exceeds *twice* <getopt-option-staleness/>
207
+ months, *append* to <info-K/> the hint `Gotcha: stale/abandoned
208
+ -- last release <age-K/> ago`; else if its age exceeds
209
+ <getopt-option-staleness/> months, *append* to <info-K/> the
210
+ hint `Gotcha: aging -- last release <age-K/> ago`. Do not output
196
211
  anything.
197
212
  </step>
198
213
 
@@ -217,11 +232,11 @@ for the technology stack to *provide* the *needed functionality*
217
232
  <template>
218
233
  <ase-tpl-bullet-normal/> **COMPONENT RANKING**:
219
234
 
220
- | ⚑ *Component* | ▣ *Package* | ❖ *Version* | ↓ *Downloads* | ⎈ *Stars* | ⏲ *Updated* | ☆ *Created* |
221
- | :------------ | :------------- | -----------: | -----------------: | -------------: | :--------------- | :----------- |
222
- | **<name-1/>** | `<package-1/>` | <version-1/> | **<downloads-1/>** | **<stars-1/>** | **<updated-1/>** | <created-1/> |
235
+ | ⚑ *Component* | ▣ *Package* | ❖ *Version* | ↓ *Downloads* | ⎈ *Stars* | ⏲ *Updated* | ☆ *Created* | ⚭ *Dep.* |
236
+ | :------------ | :------------- | -----------: | -----------------: | -------------: | :--------------- | :----------- | ---------: |
237
+ | **<name-1/>** | `<package-1/>` | <version-1/> | **<downloads-1/>** | **<stars-1/>** | **<updated-1/>** | <created-1/> | <deps-1/> |
223
238
  [...]
224
- | **<name-N/>** | `<package-N/>` | <version-N/> | **<downloads-N/>** | **<stars-N/>** | **<updated-N/>** | <created-N/> |
239
+ | **<name-N/>** | `<package-N/>` | <version-N/> | **<downloads-N/>** | **<stars-N/>** | **<updated-N/>** | <created-N/> | <deps-N/> |
225
240
  </template>
226
241
  </step>
227
242
  </flow>
@@ -8,6 +8,8 @@
8
8
  `ase-arch-discover`
9
9
  [`--help`|`-h`]
10
10
  [`--limit`|`-l=12`]
11
+ [`--staleness`|`-s=18`]
12
+ [`--small-scope`|`-S`]
11
13
  *functionality*
12
14
 
13
15
  ## DESCRIPTION
@@ -20,9 +22,13 @@ The skill determines the project's technology stack (TypeScript,
20
22
  JavaScript, Kotlin, or Java), derives essential keywords from the
21
23
  requested functionality, queries the corresponding package registry
22
24
  (NPM or Maven Central), retrieves metadata (version, downloads,
23
- stars, dates) via the `ase_component_info` MCP tool, and reports
24
- the top-ranked components as a Markdown table together with a single
25
- distinguishing hint (USP, Crux, or Gotcha) per component.
25
+ stars, dates, dependency count) via the `ase_component_info` MCP tool,
26
+ and reports the top-ranked components as a Markdown table together with
27
+ a single distinguishing hint (USP, Crux, or Gotcha) per component.
28
+
29
+ The ranking penalizes *stale* components (last release older than the
30
+ `--staleness` threshold) and, when the `--small-scope` option is given,
31
+ demotes dependency-heavy components.
26
32
 
27
33
  ## OPTIONS
28
34
 
@@ -31,6 +37,19 @@ distinguishing hint (USP, Crux, or Gotcha) per component.
31
37
  in the final ranking (default: 12). Raise it for a broader, more
32
38
  exhaustive survey, lower it for a quicker, narrower lookup.
33
39
 
40
+ `--staleness`|`-s=18`:
41
+ The *staleness threshold* in months (default: 18). A component whose
42
+ last release is older than this is rank-penalized and flagged with an
43
+ `aging` gotcha; older than *twice* the threshold, it is penalized
44
+ harder and flagged as `stale/abandoned`.
45
+
46
+ `--small-scope`|`-S`:
47
+ Treat the requested *functionality* as *small-scope* (default: off).
48
+ When enabled, the ranking demotes dependency-heavy components by a
49
+ dependency-weight penalty, since a dependency-free/hand-rolled
50
+ implementation is a realistic alternative for narrow, self-contained
51
+ functionality.
52
+
34
53
  ## ARGUMENTS
35
54
 
36
55
  *functionality*:
@@ -57,6 +76,12 @@ Discover a broader set of up to 20 HTTP client components:
57
76
  ❯ /ase-arch-discover --limit 20 HTTP client with retries
58
77
  ```
59
78
 
79
+ Discover HTTP clients, flagging any without a release in the last 12 months:
80
+
81
+ ```text
82
+ ❯ /ase-arch-discover --staleness 12 HTTP client with retries
83
+ ```
84
+
60
85
  ## SEE ALSO
61
86
 
62
87
  [`ase-arch-analyze`](../ase-arch-analyze/help.md), [`ase-meta-search`](../ase-meta-search/help.md), [`ase-meta-evaluate`](../ase-meta-evaluate/help.md).