@rse/ase 0.0.40 → 0.0.41

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 (32) hide show
  1. package/dst/ase-config.js +2 -0
  2. package/dst/ase-decision.js +67 -0
  3. package/dst/ase-service.js +25 -26
  4. package/dst/ase-skills.js +224 -0
  5. package/package.json +6 -4
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/.github/plugin/plugin.json +1 -1
  8. package/plugin/package.json +1 -1
  9. package/plugin/skills/ase-arch-analyze/SKILL.md +0 -1
  10. package/plugin/skills/ase-arch-discover/SKILL.md +44 -32
  11. package/plugin/skills/ase-code-analyze/SKILL.md +0 -1
  12. package/plugin/skills/ase-code-craft/SKILL.md +0 -1
  13. package/plugin/skills/ase-code-explain/SKILL.md +0 -1
  14. package/plugin/skills/ase-code-insight/SKILL.md +0 -1
  15. package/plugin/skills/ase-code-lint/SKILL.md +0 -1
  16. package/plugin/skills/ase-code-refactor/SKILL.md +0 -1
  17. package/plugin/skills/ase-code-resolve/SKILL.md +0 -1
  18. package/plugin/skills/ase-meta-changes/SKILL.md +0 -1
  19. package/plugin/skills/ase-meta-chat/SKILL.md +0 -1
  20. package/plugin/skills/ase-meta-commit/SKILL.md +0 -1
  21. package/plugin/skills/ase-meta-evaluate/SKILL.md +10 -8
  22. package/plugin/skills/ase-meta-quorum/SKILL.md +0 -1
  23. package/plugin/skills/ase-meta-search/SKILL.md +0 -1
  24. package/plugin/skills/ase-meta-why/SKILL.md +0 -1
  25. package/plugin/skills/ase-task-delete/SKILL.md +0 -1
  26. package/plugin/skills/ase-task-edit/SKILL.md +0 -1
  27. package/plugin/skills/ase-task-id/SKILL.md +0 -1
  28. package/plugin/skills/ase-task-implement/SKILL.md +0 -1
  29. package/plugin/skills/ase-task-list/SKILL.md +0 -1
  30. package/plugin/skills/ase-task-preflight/SKILL.md +0 -1
  31. package/plugin/skills/ase-task-reboot/SKILL.md +0 -1
  32. package/plugin/skills/ase-task-view/SKILL.md +0 -1
package/dst/ase-config.js CHANGED
@@ -485,6 +485,8 @@ export class Config {
485
485
  delete(key) {
486
486
  this.assertWritable(key);
487
487
  const td = this.docs[this.target];
488
+ if (td.doc.contents === null || td.doc.contents === undefined)
489
+ return;
488
490
  const next = td.doc.clone();
489
491
  next.deleteIn(this.resolveKey(key).split("."));
490
492
  const saved = td.doc;
@@ -0,0 +1,67 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+ import { z } from "zod";
7
+ /* reusable functionality: weighted multi-criteria decision matrix */
8
+ export class Decision {
9
+ /* compute the per-alternative product-sum (rating) row from a
10
+ weighted decision matrix. Each input row has the shape
11
+ `[weight, eval_1, eval_2, ..., eval_N]`. For each alternative
12
+ column j (1..N), the result is the sum over all rows K of
13
+ `weight_K * eval_K_j`. The output array has length N. */
14
+ static productSum(matrix) {
15
+ if (matrix.length === 0)
16
+ return [];
17
+ const cols = matrix[0].length;
18
+ if (cols < 2)
19
+ throw new Error("each row must contain a weight followed by at least one evaluation column");
20
+ const N = cols - 1;
21
+ const ratings = new Array(N).fill(0);
22
+ for (let i = 0; i < matrix.length; i++) {
23
+ const row = matrix[i];
24
+ if (row.length !== cols)
25
+ throw new Error(`row ${i} has ${row.length} columns, expected ${cols}`);
26
+ const weight = row[0];
27
+ for (let j = 0; j < N; j++)
28
+ ratings[j] += weight * row[j + 1];
29
+ }
30
+ return ratings;
31
+ }
32
+ }
33
+ /* MCP registration entry point for the decision-matrix tool */
34
+ export class DecisionMCP {
35
+ register(mcp) {
36
+ mcp.registerTool("decision_matrix", {
37
+ title: "ASE decision matrix",
38
+ description: "Compute the per-alternative product-sum (rating) row of a weighted " +
39
+ "multi-criteria decision matrix. The input `matrix` is an array of rows, " +
40
+ "one row per criterion, where each row has the shape " +
41
+ "`[weight, eval_1, eval_2, ..., eval_N]` (i.e. the criterion weight " +
42
+ "followed by N evaluation values, one per alternative). For each " +
43
+ "alternative column j (1..N), the result is the sum over all rows K of " +
44
+ "`weight_K * eval_K_j`. Returns a JSON `text` array of length N with " +
45
+ "the raw, unrounded ratings (one per alternative, in the same column " +
46
+ "order as the input).",
47
+ inputSchema: {
48
+ matrix: z.array(z.array(z.number()))
49
+ .describe("Decision matrix rows: each row is `[weight, eval_1, ..., eval_N]`")
50
+ }
51
+ }, async (args) => {
52
+ try {
53
+ const result = Decision.productSum(args.matrix);
54
+ return {
55
+ content: [{ type: "text", text: JSON.stringify(result) }]
56
+ };
57
+ }
58
+ catch (err) {
59
+ const message = err instanceof Error ? err.message : String(err);
60
+ return {
61
+ isError: true,
62
+ content: [{ type: "text", text: `ERROR: ${message}` }]
63
+ };
64
+ }
65
+ });
66
+ }
67
+ }
@@ -9,7 +9,7 @@ import net from "node:net";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { spawn } from "node:child_process";
11
11
  import Hapi from "@hapi/hapi";
12
- import axios from "axios";
12
+ import { ofetch } from "ofetch";
13
13
  import { isMap } from "yaml";
14
14
  import prettyMs from "pretty-ms";
15
15
  import * as v from "valibot";
@@ -22,6 +22,7 @@ import { KVMCP } from "./ase-kv.js";
22
22
  import PersonaMCP from "./ase-persona.js";
23
23
  import { TimestampMCP } from "./ase-timestamp.js";
24
24
  import { GetoptMCP } from "./ase-getopt.js";
25
+ import { SkillsMCP } from "./ase-skills.js";
25
26
  import pkg from "../package.json" with { type: "json" };
26
27
  /* shared service host */
27
28
  export const SERVICE_HOST = "127.0.0.1";
@@ -30,23 +31,24 @@ const HOST = SERVICE_HOST;
30
31
  export const serviceSchema = v.nullish(v.strictObject({
31
32
  port: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1024), v.maxValue(65535)))
32
33
  }));
33
- /* distinguish ECONNREFUSED from other Axios transport errors */
34
+ /* distinguish ECONNREFUSED from other ofetch transport errors */
34
35
  export const isConnRefused = (err) => {
35
36
  const e = err;
36
- return e?.code === "ECONNREFUSED" || e?.cause?.code === "ECONNREFUSED";
37
+ return e?.code === "ECONNREFUSED"
38
+ || e?.cause?.code === "ECONNREFUSED"
39
+ || e?.cause?.cause?.code === "ECONNREFUSED";
37
40
  };
38
41
  /* probe the service and verify ASE identity banner */
39
42
  export const probe = async (port, projectId) => {
40
43
  try {
41
- const r = await axios.request({
44
+ const r = await ofetch.raw(`http://${SERVICE_HOST}:${port}/`, {
42
45
  method: "OPTIONS",
43
- url: `http://${SERVICE_HOST}:${port}/`,
44
- timeout: 2000,
45
- validateStatus: () => true
46
+ signal: AbortSignal.timeout(2000),
47
+ ignoreResponseError: true
46
48
  });
47
49
  if (r.status < 200 || r.status >= 300)
48
50
  return false;
49
- const d = r.data;
51
+ const d = r._data;
50
52
  return d?.ase === true && d?.projectId === projectId;
51
53
  }
52
54
  catch (err) {
@@ -238,6 +240,7 @@ export default class ServiceCommand {
238
240
  new PersonaMCP(this.log).register(mcp);
239
241
  new TimestampMCP().register(mcp);
240
242
  new GetoptMCP().register(mcp);
243
+ new SkillsMCP().register(mcp);
241
244
  return mcp;
242
245
  };
243
246
  /* listen to HTTP/REST endpoints */
@@ -466,15 +469,14 @@ export default class ServiceCommand {
466
469
  }
467
470
  const match = await probe(ctx.port, ctx.projectId);
468
471
  if (match === true) {
469
- const r = await axios.request({
472
+ const r = await ofetch.raw(`http://${HOST}:${ctx.port}/command`, {
470
473
  method: "POST",
471
- url: `http://${HOST}:${ctx.port}/command`,
472
474
  headers: { "Content-Type": "application/json" },
473
- data: { command: "status" },
474
- timeout: 2000,
475
- validateStatus: () => true
475
+ body: { command: "status" },
476
+ signal: AbortSignal.timeout(2000),
477
+ ignoreResponseError: true
476
478
  });
477
- const d = r.data;
479
+ const d = r._data;
478
480
  const uptimeMs = typeof d?.uptimeMs === "number" ? d.uptimeMs : 0;
479
481
  const uptime = prettyMs(uptimeMs, { verbose: true });
480
482
  process.stdout.write(`service: running on port ${ctx.port} (uptime: ${uptime})\n`);
@@ -503,17 +505,15 @@ export default class ServiceCommand {
503
505
  if (ctx.port === null)
504
506
  throw new Error("service not running (no port configured after auto-start)");
505
507
  }
506
- const r = await axios.request({
508
+ const r = await ofetch.raw(`http://${HOST}:${ctx.port}/command`, {
507
509
  method: "POST",
508
- url: `http://${HOST}:${ctx.port}/command`,
509
510
  headers: { "Content-Type": "application/json" },
510
- data: { command: cmd },
511
- timeout: 5000,
512
- validateStatus: () => true,
513
- responseType: "text",
514
- transformResponse: [(x) => x]
511
+ body: { command: cmd },
512
+ signal: AbortSignal.timeout(5000),
513
+ ignoreResponseError: true,
514
+ responseType: "text"
515
515
  });
516
- const body = typeof r.data === "string" ? r.data : JSON.stringify(r.data);
516
+ const body = typeof r._data === "string" ? r._data : JSON.stringify(r._data);
517
517
  process.stdout.write(body);
518
518
  if (!body.endsWith("\n"))
519
519
  process.stdout.write("\n");
@@ -536,11 +536,10 @@ export default class ServiceCommand {
536
536
  Service.clearPort(ctx.svc);
537
537
  return 0;
538
538
  }
539
- const r = await axios.request({
539
+ const r = await ofetch.raw(`http://${HOST}:${ctx.port}/stop`, {
540
540
  method: "GET",
541
- url: `http://${HOST}:${ctx.port}/stop`,
542
- timeout: 5000,
543
- validateStatus: () => true
541
+ signal: AbortSignal.timeout(5000),
542
+ ignoreResponseError: true
544
543
  });
545
544
  const ok = r.status >= 200 && r.status < 300;
546
545
  Service.clearPort(ctx.svc);
@@ -0,0 +1,224 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+ import { z } from "zod";
7
+ import { ofetch } from "ofetch";
8
+ import pacote from "pacote";
9
+ /* reusable functionality: gather per-package metadata in maximum parallel */
10
+ export class Skills {
11
+ /* HTTP timeout for the GitHub/npm-downloads side calls */
12
+ static HTTP_TIMEOUT_MS = 10_000;
13
+ /* cap concurrent ofetch web requests to avoid hammering the remote
14
+ endpoints (GitHub API, npm downloads API) */
15
+ static HTTP_CONCURRENCY = 4;
16
+ static httpActive = 0;
17
+ static httpQueue = [];
18
+ static async httpLimit(fn) {
19
+ if (Skills.httpActive >= Skills.HTTP_CONCURRENCY)
20
+ await new Promise((resolve) => Skills.httpQueue.push(resolve));
21
+ Skills.httpActive++;
22
+ try {
23
+ return await fn();
24
+ }
25
+ finally {
26
+ Skills.httpActive--;
27
+ const next = Skills.httpQueue.shift();
28
+ if (next !== undefined)
29
+ next();
30
+ }
31
+ }
32
+ /* fetch the full registry packument for a single package */
33
+ static async fetchPackument(name) {
34
+ try {
35
+ const pkg = await pacote.packument(name, { fullMetadata: true });
36
+ const version = pkg["dist-tags"]?.latest ?? "";
37
+ const time = pkg.time ?? {};
38
+ const verEntry = version !== "" ? pkg.versions?.[version] : undefined;
39
+ let repository = "";
40
+ if (verEntry !== undefined) {
41
+ const r = verEntry.repository;
42
+ if (typeof r === "string")
43
+ repository = r;
44
+ else if (r !== undefined && typeof r.url === "string")
45
+ repository = r.url;
46
+ }
47
+ return { version, time, repository };
48
+ }
49
+ catch {
50
+ return { version: "", time: {}, repository: "" };
51
+ }
52
+ }
53
+ /* fetch GitHub stars given a repository URL (or empty string) */
54
+ static async fetchStars(repository) {
55
+ const m = /^.+?\/\/github\.com\/([^/]+\/[^/#?]+?)(?:\.git)?(?:[/#?].*)?$/.exec(repository);
56
+ if (m === null)
57
+ return "N.A.";
58
+ try {
59
+ const data = await Skills.httpLimit(() => ofetch(`https://api.github.com/repos/${m[1]}`, { timeout: Skills.HTTP_TIMEOUT_MS, ignoreResponseError: true }));
60
+ const n = data?.stargazers_count;
61
+ return typeof n === "number" ? n : "N.A.";
62
+ }
63
+ catch {
64
+ return "N.A.";
65
+ }
66
+ }
67
+ /* fetch last-month npm downloads for a single package */
68
+ static async fetchDownloads(name) {
69
+ try {
70
+ const data = await Skills.httpLimit(() => ofetch(`https://api.npmjs.org/downloads/point/last-month/${encodeURIComponent(name)}`, { timeout: Skills.HTTP_TIMEOUT_MS, ignoreResponseError: true }));
71
+ const n = data?.downloads;
72
+ return typeof n === "number" ? n : "N.A.";
73
+ }
74
+ catch {
75
+ return "N.A.";
76
+ }
77
+ }
78
+ /* gather metadata for all given components in maximum parallel,
79
+ dispatching on the technology stack:
80
+ - "JavaScript"/"TypeScript": NPM registry (pacote) + GitHub stars + npm-downloads
81
+ - "Java"/"Kotlin"/"Unknown": not yet supported -- return empty result */
82
+ static async info(stack, components) {
83
+ /* FIXME: currently just limited technology stack support */
84
+ if (stack !== "JavaScript" && stack !== "TypeScript")
85
+ return [];
86
+ /* per package: kick off packument and downloads in parallel,
87
+ then stars as soon as the packument resolves; across packages
88
+ everything runs concurrently via Promise.all */
89
+ const results = await Promise.all(components.map(async (name) => {
90
+ const packumentPromise = Skills.fetchPackument(name);
91
+ const downloadsPromise = Skills.fetchDownloads(name);
92
+ const starsPromise = packumentPromise.then((p) => Skills.fetchStars(p.repository));
93
+ const [p, downloads, stars] = await Promise.all([
94
+ packumentPromise, downloadsPromise, starsPromise
95
+ ]);
96
+ const created = p.time.created ?? "";
97
+ const updated = p.version !== "" ? (p.time[p.version] ?? "") : "";
98
+ const rank = Skills.computeRank(downloads, stars, created, updated);
99
+ return {
100
+ name,
101
+ version: p.version,
102
+ created,
103
+ updated,
104
+ repository: p.repository,
105
+ stars,
106
+ downloads,
107
+ rank
108
+ };
109
+ }));
110
+ /* sort by rank in descending order (best first) */
111
+ results.sort((a, b) => b.rank - a.rank);
112
+ return results;
113
+ }
114
+ /* compute composite rank score from weighted metrics:
115
+ downloads x
116
+ stars x
117
+ ([lifespan =] (updated - created)) x
118
+ ([recentness =] exp(-(now - updated) / halfLife)) */
119
+ static computeRank(downloads, stars, created, updated) {
120
+ const d = typeof downloads === "number" ? downloads : 0;
121
+ const s = typeof stars === "number" ? stars : 0;
122
+ const cMs = created !== "" ? Date.parse(created) : NaN;
123
+ const uMs = updated !== "" ? Date.parse(updated) : NaN;
124
+ if (Number.isNaN(cMs) || Number.isNaN(uMs))
125
+ return 0;
126
+ const now = Date.now();
127
+ const msPerDay = 1000 * 60 * 60 * 24;
128
+ const halfLife = 365 / 2;
129
+ const lifespan = Math.max(0, uMs - cMs);
130
+ const ageDays = Math.max(0, (now - uMs) / msPerDay);
131
+ const recentness = Math.exp(-ageDays / halfLife);
132
+ return d * s * lifespan * recentness;
133
+ }
134
+ /* compute the per-alternative product-sum (rating) row from a
135
+ weighted decision matrix. Each input row has the shape
136
+ `[ weight, eval_1, eval_2, ..., eval_N ]`. For each alternative
137
+ column j (1..N), the result is the sum over all rows K of
138
+ `weight_K * eval_K_j`. The output array has length N. */
139
+ static productSum(matrix) {
140
+ if (matrix.length === 0)
141
+ return [];
142
+ const cols = matrix[0].length;
143
+ if (cols < 2)
144
+ throw new Error("each row must contain a weight followed by at least one evaluation column");
145
+ const N = cols - 1;
146
+ const ratings = new Array(N).fill(0);
147
+ for (let i = 0; i < matrix.length; i++) {
148
+ const row = matrix[i];
149
+ if (row.length !== cols)
150
+ throw new Error(`row ${i} has ${row.length} columns, expected ${cols}`);
151
+ const weight = row[0];
152
+ for (let j = 0; j < N; j++)
153
+ ratings[j] += weight * row[j + 1];
154
+ }
155
+ return ratings;
156
+ }
157
+ }
158
+ /* MCP registration entry point for various skill helper tools */
159
+ export class SkillsMCP {
160
+ register(mcp) {
161
+ mcp.registerTool("component_info", {
162
+ title: "ASE component info",
163
+ description: "Gather metadata for a list of NPM packages in maximum parallel. " +
164
+ "For each package, fetches the full registry packument via `pacote` " +
165
+ "(in-process, no `npm view` subprocess), the GitHub `stargazers_count` " +
166
+ "from `api.github.com` (if the repository points to GitHub), and the " +
167
+ "last-month downloads from `api.npmjs.org`. Returns a JSON `text` array " +
168
+ "of `{ name, version, created, updated, repository, stars, downloads, rank }` " +
169
+ "objects, sorted in descending order by `rank`. Failures of " +
170
+ "individual side calls are isolated and reported as `\"N.A.\"` or empty " +
171
+ "string so every entry has the full shape.",
172
+ inputSchema: {
173
+ stack: z.string()
174
+ .describe("Technology stack: \"JavaScript\", \"TypeScript\", \"Java\", \"Kotlin\", or \"Unknown\""),
175
+ components: z.array(z.string())
176
+ .describe("List of package names to gather metadata for")
177
+ }
178
+ }, async (args) => {
179
+ try {
180
+ const result = await Skills.info(args.stack, args.components);
181
+ return {
182
+ content: [{ type: "text", text: JSON.stringify(result) }]
183
+ };
184
+ }
185
+ catch (err) {
186
+ const message = err instanceof Error ? err.message : String(err);
187
+ return {
188
+ isError: true,
189
+ content: [{ type: "text", text: `ERROR: ${message}` }]
190
+ };
191
+ }
192
+ });
193
+ mcp.registerTool("decision_matrix", {
194
+ title: "ASE decision matrix",
195
+ description: "Compute the per-alternative product-sum (rating) row of a weighted " +
196
+ "multi-criteria decision matrix. The input `matrix` is an array of rows, " +
197
+ "one row per criterion, where each row has the shape " +
198
+ "`[ weight, eval_1, eval_2, ..., eval_N ]` (i.e. the criterion weight " +
199
+ "followed by N evaluation values, one per alternative). For each " +
200
+ "alternative column j (1..N), the result is the sum over all rows K of " +
201
+ "`weight_K * eval_K_j`. Returns a JSON `text` array of length N with " +
202
+ "the raw, unrounded ratings (one per alternative, in the same column " +
203
+ "order as the input).",
204
+ inputSchema: {
205
+ matrix: z.array(z.array(z.number()))
206
+ .describe("Decision matrix rows: each row is `[weight, eval_1, ..., eval_N]`")
207
+ }
208
+ }, async (args) => {
209
+ try {
210
+ const result = Skills.productSum(args.matrix);
211
+ return {
212
+ content: [{ type: "text", text: JSON.stringify(result) }]
213
+ };
214
+ }
215
+ catch (err) {
216
+ const message = err instanceof Error ? err.message : String(err);
217
+ return {
218
+ isError: true,
219
+ content: [{ type: "text", text: `ERROR: ${message}` }]
220
+ };
221
+ }
222
+ });
223
+ }
224
+ }
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.0.40",
9
+ "version": "0.0.41",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -36,7 +36,8 @@
36
36
  "@types/update-notifier": "6.0.8",
37
37
  "@types/shell-quote": "1.7.5",
38
38
  "@types/proper-lockfile": "4.1.4",
39
- "@types/write-file-atomic": "4.0.3"
39
+ "@types/write-file-atomic": "4.0.3",
40
+ "@types/pacote": "11.1.8"
40
41
  },
41
42
  "dependencies": {
42
43
  "commander": "14.0.3",
@@ -45,7 +46,6 @@
45
46
  "execa": "9.6.1",
46
47
  "mkdirp": "3.0.1",
47
48
  "@hapi/hapi": "21.4.9",
48
- "axios": "1.16.1",
49
49
  "beautiful-mermaid": "1.1.3",
50
50
  "cli-table3": "0.6.5",
51
51
  "chalk": "5.6.2",
@@ -57,7 +57,9 @@
57
57
  "update-notifier": "7.3.1",
58
58
  "shell-quote": "1.8.3",
59
59
  "proper-lockfile": "4.1.2",
60
- "write-file-atomic": "4.0.0"
60
+ "write-file-atomic": "4.0.0",
61
+ "pacote": "21.5.0",
62
+ "ofetch": "1.4.1"
61
63
  },
62
64
  "engines": {
63
65
  "npm": ">=10.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
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.0.40",
3
+ "version": "0.0.41",
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.0.40",
9
+ "version": "0.0.41",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -100,7 +100,6 @@ allowed-tools:
100
100
  ---
101
101
 
102
102
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
103
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
104
103
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
105
104
 
106
105
  Review Software Architecture
@@ -9,14 +9,11 @@ disable-model-invocation: false
9
9
  effort: medium
10
10
  allowed-tools:
11
11
  - "Bash(npm search --json *)"
12
- - "Bash(npm view --json *)"
13
12
  - "Skill"
14
13
  - "Agent"
15
- - "WebFetch"
16
14
  ---
17
15
 
18
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
19
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
20
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
21
18
 
22
19
  Discover Components
@@ -113,26 +110,6 @@ for the technology stack to *provide* the *needed functionality*
113
110
  (L=1-M). Merge the results into the already existing result
114
111
  set, but deduplicate entries.
115
112
 
116
- - For each discovered *NPM package* <component-K/> (K=1-N),
117
- use the shell command `npm view --json "<package-K/>"
118
- version time repository.url` to discover
119
- its version <version-K/>, the publish time of that
120
- version <updated-K/> (read from `time[<version-K/>]`),
121
- its time created <created-K/> (read from `time.created`),
122
- and its repository URL <repository-K/>.
123
-
124
- - If the <repository-K/> regexp-matches
125
- `.+?//github\.com/([^/]+/[^/.]+).*` use the `WebFetch` tool
126
- to fetch the URL `https://api.github.com/repos/$1` (`$1`
127
- is the value matched by the first capturing parenthesis
128
- in the regexp) and extract <stars-K/> from its JSON
129
- `stargazers_count` field, else set <stars-K/> to `N.A.`.
130
-
131
- - For each discovered *NPM package* <component-K/>
132
- (K=1-N), use the `WebFetch` tool on the URL
133
- `https://api.npmjs.org/downloads/point/last-month/<package-K/>`
134
- to extract the <downloads-K/> from the `downloads` field.
135
-
136
113
  - If <stack/> is "Java" or "Kotlin":
137
114
 
138
115
  - Based on the essential keywords <keyword-L/> (L=1-M),
@@ -140,25 +117,60 @@ for the technology stack to *provide* the *needed functionality*
140
117
  discover an initial set of a maximum of 12 *Java packages*
141
118
  <component-K/> and at least their real name <name-K/> and
142
119
  their unique package names <package-K/>.
120
+
121
+ - Call the `component_info(stack: <stack/>, components:
122
+ [ <package-1/>, ..., <package-N/> ])` tool of the `ase` MCP
123
+ service *once* for the entire set of discovered packages.
124
+ The tool dispatches internally on <stack/> and fetches all
125
+ metadata in maximum parallel and returns an array of objects `{
126
+ name, version, time, repository, stars, downloads }`. For each
127
+ component <component-K/> (K=1-N) read from its corresponding
128
+ entry: <version-K/> from `version`, <updated-K/> from `updated`,
129
+ <created-K/> from `created`, <repository-K/> from `repository`,
130
+ <stars-K/> from `stars` (numeric or `N.A.`), <downloads-K/>
131
+ from `downloads` (numeric or `N.A.`) and <rank-K/> from `rank`
132
+ (numeric).
133
+
134
+ - Sort, in descending order, the discovered components
135
+ <component-K/> (K=1-N) by their `rank` field and trim the result
136
+ list to just a maximum of 12 total components.
137
+
138
+ - For each component <component-K/> (K=1-N), research and then
139
+ decide which *one* of *USP* (Unique Selling Point -- what makes
140
+ it unique), *Crux* (what you should notice), or *Gotcha* (what
141
+ you should not stumble over) is its single most distinguishing
142
+ perspective, and remember this as an <info-K/> (K=1-N) formatted
143
+ like `<type/>: <hint/>` where <type/> is one of `USP`, `Crux`,
144
+ or `Gotcha` and <hint/> is a 1-6 word hint. Do not output
145
+ anything.
143
146
  </step>
144
147
 
145
148
  4. <step id="STEP 4: Report Components">
146
- - Sort, in descending order, the discovered components
147
- <component-K/> (K=1-N) first by their <downloads-K/> and second
148
- by their <stars-K/> and trim the result list to just a maximum
149
- of 12 total components.
149
+ - Display the determined, individual components as a Markdown
150
+ *table* with just the following <template/> and do not output
151
+ anything else:
152
+
153
+ <template>
154
+ &#x1F535; **COMPONENT HINTS**:
155
+
156
+ | ⚑ *Component* | ▣ *Package* | ⚖ *Hint* |
157
+ | :----------------- | :------------- | :-------- |
158
+ | **<component-1/>** | `<package-1/>` | <info-1/> |
159
+ [...]
160
+ | **<component-N/>** | `<package-N/>` | <info-N/> |
161
+ </template>
150
162
 
151
163
  - Display the discovered components as a Markdown *table*
152
164
  with just the following <template/>:
153
165
 
154
166
  <template>
155
- &#x1F535; **COMPONENTS**:
167
+ &#x1F535; **COMPONENT RANKING**:
156
168
 
157
- | *Component* | *Package* | *Version* | *Downloads* | *Stars* | *Updated* | *Created* |
158
- | :------------ | :------------- | -----------: | -----------------: | -------------: | :--------------- | :----------- |
159
- | **<name-1/>** | `<package-1/>` | <version-1/> | **<downloads-1/>** | **<stars-1/>** | **<updated-1/>** | <created-1/> |
169
+ | *Component* | *Package* | *Version* | *Downloads* | *Stars* | *Updated* | *Created* |
170
+ | :----------------- | :------------- | -----------: | -----------------: | -------------: | :--------------- | :----------- |
171
+ | **<component-1/>** | `<package-1/>` | <version-1/> | **<downloads-1/>** | **<stars-1/>** | **<updated-1/>** | <created-1/> |
160
172
  [...]
161
- | **<name-N/>** | `<package-N/>` | <version-N/> | **<downloads-N/>** | **<stars-N/>** | **<updated-N/>** | <created-N/> |
173
+ | **<component-N/>** | `<package-N/>` | <version-N/> | **<downloads-N/>** | **<stars-N/>** | **<updated-N/>** | <created-N/> |
162
174
  </template>
163
175
  </step>
164
176
  </flow>
@@ -11,7 +11,6 @@ allowed-tools:
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
 
17
16
  Analyze Source Code
@@ -12,7 +12,6 @@ allowed-tools:
12
12
  ---
13
13
 
14
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
15
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
18
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -13,7 +13,6 @@ allowed-tools:
13
13
  ---
14
14
 
15
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
16
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
18
17
 
19
18
  Explain Source Code
@@ -15,7 +15,6 @@ allowed-tools:
15
15
  ---
16
16
 
17
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
18
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
19
18
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
20
19
 
21
20
  Project Insight
@@ -9,7 +9,6 @@ effort: medium
9
9
  ---
10
10
 
11
11
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
12
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
13
12
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
14
13
 
15
14
  Lint Source Code
@@ -12,7 +12,6 @@ allowed-tools:
12
12
  ---
13
13
 
14
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
15
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
18
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -12,7 +12,6 @@ allowed-tools:
12
12
  ---
13
13
 
14
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
15
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
18
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -15,7 +15,6 @@ allowed-tools:
15
15
  ---
16
16
 
17
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
18
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
19
18
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
20
19
 
21
20
  Update ChangeLog Entries
@@ -19,7 +19,6 @@ allowed-tools:
19
19
  ---
20
20
 
21
21
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
22
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
23
22
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
24
23
 
25
24
  Query Foreign LLMs for Chat
@@ -11,7 +11,6 @@ allowed-tools:
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
 
17
16
  Git Commit
@@ -12,7 +12,6 @@ effort: high
12
12
  ---
13
13
 
14
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
15
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
17
16
 
18
17
  Evaluate Alternatives
@@ -153,13 +152,16 @@ multi-*criteria* decision matrix.
153
152
  from { -2, -1, 0, +1, +2 } (from worst, over neutral, to best). Do
154
153
  not output anything.
155
154
 
156
- - Then, for each alternative <alternative-K/> (K=1-N), calculate a
157
- rating <rating-K/> (K=1-N) which is the product-sum of all weights
158
- <weight-L/> (L=1-M) and the evaluation <eval-K-L/> (K=1-N, L=1-M).
159
- The result is always a numerical value. Retain the *raw, unrounded*
160
- <rating-K/> for use in STEP 5, but round <rating-K/> to 2 decimal
161
- places *for display only* when emitting it in the table below. Do
162
- not output anything.
155
+ - Then, calculate the ratings <rating-K/> (K=1-N) for all
156
+ alternatives in a single call by invoking the `decision_matrix(matrix:
157
+ [ [ <weight-1/>, <eval-1-1/>, ..., <eval-1-N/> ], ..., [ <weight-M/>,
158
+ <eval-M-1/>, ..., <eval-M-N/> ] ])` tool of the `ase` MCP service.
159
+ The tool returns an array of N numerical values, where the K-th
160
+ entry is the product-sum of all weights <weight-L/> (L=1-M) and
161
+ the evaluation <eval-K-L/> (L=1-M) for alternative <alternative-K/>.
162
+ Retain the *raw, unrounded* <rating-K/> for use in STEP 5, but
163
+ round <rating-K/> to 2 decimal places *for display only* when
164
+ emitting it in the table below. Do not output anything.
163
165
 
164
166
  - Output the resulting *Weighted Decision Matrix* as a Markdown
165
167
  *table* with just the following <template/> and do not output
@@ -12,7 +12,6 @@ allowed-tools:
12
12
  ---
13
13
 
14
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
15
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
17
16
 
18
17
  Query Multiple AIs for Quorum Answer
@@ -16,7 +16,6 @@ allowed-tools:
16
16
  ---
17
17
 
18
18
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
19
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
20
19
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
21
20
 
22
21
  Search the Internet/Web
@@ -9,7 +9,6 @@ effort: medium
9
9
  ---
10
10
 
11
11
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
12
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
13
12
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
14
13
 
15
14
  Five-Whys Root-Cause Analysis
@@ -11,7 +11,6 @@ effort: low
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
 
17
16
  Delete a Task Plan
@@ -13,7 +13,6 @@ effort: high
13
13
  ---
14
14
 
15
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
16
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
18
17
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
19
18
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -11,7 +11,6 @@ effort: low
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
 
17
16
  Task Configuration
@@ -11,7 +11,6 @@ effort: xhigh
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -10,7 +10,6 @@ effort: low
10
10
  ---
11
11
 
12
12
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
13
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
14
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
16
15
 
@@ -11,7 +11,6 @@ effort: xhigh
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -11,7 +11,6 @@ effort: xhigh
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
  @${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
17
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
@@ -11,7 +11,6 @@ effort: low
11
11
  ---
12
12
 
13
13
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
14
- @${CLAUDE_SKILL_DIR}/../../meta/ase-persona.md
15
14
  @${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
16
15
 
17
16
  View a Task Plan