@rse/ase 0.0.46 → 0.0.49

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 (34) hide show
  1. package/dst/ase-config.js +103 -6
  2. package/dst/ase-diagram.js +3 -3
  3. package/dst/ase-getopt.js +8 -4
  4. package/dst/ase-hook.js +122 -23
  5. package/dst/ase-mcp.js +26 -20
  6. package/dst/ase-service.js +10 -3
  7. package/dst/ase-skills.js +182 -42
  8. package/dst/ase-statusline.js +17 -21
  9. package/dst/ase-task.js +60 -1
  10. package/package.json +1 -1
  11. package/plugin/.claude-plugin/plugin.json +1 -1
  12. package/plugin/.github/plugin/plugin.json +1 -1
  13. package/plugin/agents/ase-code-lint.md +370 -0
  14. package/plugin/agents/ase-docs-proofread.md +100 -0
  15. package/plugin/meta/ase-constitution.md +7 -7
  16. package/plugin/meta/ase-control.md +1 -1
  17. package/plugin/meta/ase-dialog.md +2 -2
  18. package/plugin/meta/ase-persona.md +2 -2
  19. package/plugin/meta/ase-plan.md +2 -2
  20. package/plugin/meta/ase-skill.md +2 -2
  21. package/plugin/package.json +1 -1
  22. package/plugin/skills/ase-arch-analyze/SKILL.md +4 -4
  23. package/plugin/skills/ase-arch-discover/SKILL.md +14 -3
  24. package/plugin/skills/ase-code-analyze/SKILL.md +2 -2
  25. package/plugin/skills/ase-code-explain/SKILL.md +1 -1
  26. package/plugin/skills/ase-code-lint/SKILL.md +179 -298
  27. package/plugin/skills/ase-code-resolve/SKILL.md +1 -1
  28. package/plugin/skills/ase-docs-proofread/SKILL.md +151 -51
  29. package/plugin/skills/ase-meta-changes/SKILL.md +4 -4
  30. package/plugin/skills/ase-meta-diagram/SKILL.md +5 -5
  31. package/plugin/skills/ase-meta-evaluate/SKILL.md +2 -2
  32. package/plugin/skills/ase-meta-persona/SKILL.md +1 -1
  33. package/plugin/skills/ase-meta-quorum/SKILL.md +1 -1
  34. package/plugin/skills/ase-task-rename/SKILL.md +92 -0
package/dst/ase-skills.js CHANGED
@@ -75,50 +75,184 @@ export class Skills {
75
75
  return "N.A.";
76
76
  }
77
77
  }
78
+ /* fetch Maven Central metadata for a single coordinate `groupId:artifactId`:
79
+ the Solr search endpoint provides the latest version + a `timestamp` (ms
80
+ epoch) of that release; a second `core=gav` query yields the earliest
81
+ known release, which we treat as the "created" date. The latest POM is
82
+ then downloaded directly from the repo to extract the SCM/project URL. */
83
+ static async fetchMavenInfo(coord) {
84
+ const [groupId, artifactId] = coord.split(":", 2);
85
+ if (groupId === undefined || artifactId === undefined || groupId === "" || artifactId === "")
86
+ return { version: "", created: "", updated: "", repository: "" };
87
+ try {
88
+ const latest = await Skills.httpLimit(() => ofetch("https://search.maven.org/solrsearch/select" +
89
+ `?q=g:%22${encodeURIComponent(groupId)}%22+AND+a:%22${encodeURIComponent(artifactId)}%22` +
90
+ "&rows=1&wt=json", { timeout: Skills.HTTP_TIMEOUT_MS, ignoreResponseError: true }));
91
+ const doc = latest?.response?.docs?.[0];
92
+ const version = doc?.latestVersion ?? doc?.v ?? "";
93
+ const updated = typeof doc?.timestamp === "number" ? new Date(doc.timestamp).toISOString() : "";
94
+ /* Maven Central caps `rows` at 20 and ignores client `sort`,
95
+ so paginate via `start=` to walk all versions, accumulate the
96
+ minimum timestamp client-side */
97
+ const baseUrl = "https://search.maven.org/solrsearch/select" +
98
+ `?q=g:%22${encodeURIComponent(groupId)}%22+AND+a:%22${encodeURIComponent(artifactId)}%22` +
99
+ "&core=gav&rows=20&wt=json";
100
+ let firstTs;
101
+ let start = 0;
102
+ for (let page = 0; page < 50; page++) {
103
+ const chunk = await Skills.httpLimit(() => ofetch(`${baseUrl}&start=${start}`, { timeout: Skills.HTTP_TIMEOUT_MS, ignoreResponseError: true }));
104
+ const docs = chunk?.response?.docs ?? [];
105
+ for (const d of docs) {
106
+ if (typeof d.timestamp === "number" && (firstTs === undefined || d.timestamp < firstTs))
107
+ firstTs = d.timestamp;
108
+ }
109
+ start += docs.length;
110
+ const total = chunk?.response?.numFound ?? 0;
111
+ if (docs.length === 0 || start >= total)
112
+ break;
113
+ }
114
+ const created = typeof firstTs === "number" ? new Date(firstTs).toISOString() : updated;
115
+ let repository = "";
116
+ if (version !== "") {
117
+ try {
118
+ const pom = await Skills.httpLimit(() => ofetch(`https://repo1.maven.org/maven2/${groupId.replace(/\./g, "/")}` +
119
+ `/${artifactId}/${version}/${artifactId}-${version}.pom`, { timeout: Skills.HTTP_TIMEOUT_MS, ignoreResponseError: true, responseType: "text" }));
120
+ if (typeof pom === "string") {
121
+ const scm = /<scm>[\s\S]*?<url>\s*([^<\s]+)\s*<\/url>[\s\S]*?<\/scm>/i.exec(pom);
122
+ if (scm !== null)
123
+ repository = scm[1];
124
+ else {
125
+ const url = /<project\b[\s\S]*?<url>\s*([^<\s]+)\s*<\/url>/i.exec(pom);
126
+ if (url !== null)
127
+ repository = url[1];
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ repository = "";
133
+ }
134
+ }
135
+ return { version, created, updated, repository };
136
+ }
137
+ catch {
138
+ return { version: "", created: "", updated: "", repository: "" };
139
+ }
140
+ }
141
+ /* fetch the "Used By" count from `mvnrepository.com` as a downloads proxy
142
+ for a Maven coordinate (Maven Central exposes no per-artifact download
143
+ counts). The label `Used By (NNK)` on the artifact landing page denotes
144
+ the number of other published artifacts depending on it. Note: the site
145
+ is fronted by *Cloudflare* and frequently serves a JS-challenge to
146
+ non-browser clients regardless of `User-Agent` (the block is driven by
147
+ TLS/HTTP2 fingerprinting, not headers). A realistic browser UA is
148
+ best-effort and degrades gracefully to `"N.A."` when blocked. */
149
+ static async fetchMavenDownloads(coord) {
150
+ const [groupId, artifactId] = coord.split(":", 2);
151
+ if (groupId === undefined || artifactId === undefined || groupId === "" || artifactId === "")
152
+ return "N.A.";
153
+ try {
154
+ const html = await Skills.httpLimit(() => ofetch(`https://mvnrepository.com/artifact/${encodeURIComponent(groupId)}/${encodeURIComponent(artifactId)}`, {
155
+ timeout: Skills.HTTP_TIMEOUT_MS,
156
+ ignoreResponseError: true,
157
+ responseType: "text",
158
+ headers: {
159
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
160
+ "AppleWebKit/537.36 (KHTML, like Gecko) " +
161
+ "Chrome/131.0.0.0 Safari/537.36"
162
+ }
163
+ }));
164
+ if (typeof html !== "string")
165
+ return "N.A.";
166
+ const m = /Used\s+By\s*\(\s*([\d.,]+)\s*([KMB]?)\s*\)/i.exec(html);
167
+ if (m === null)
168
+ return "N.A.";
169
+ const base = parseFloat(m[1].replace(/,/g, ""));
170
+ const mult = m[2] === "K" ? 1_000 : m[2] === "M" ? 1_000_000 : m[2] === "B" ? 1_000_000_000 : 1;
171
+ const n = Math.round(base * mult);
172
+ return Number.isFinite(n) ? n : "N.A.";
173
+ }
174
+ catch {
175
+ return "N.A.";
176
+ }
177
+ }
78
178
  /* gather metadata for all given components with maximum parallelism,
79
179
  dispatching on the technology stack:
80
180
  - "JavaScript"/"TypeScript": NPM registry (pacote) + GitHub stars + npm-downloads
81
- - "Java"/"Kotlin"/"Unknown": not yet supported -- return empty result */
181
+ - "Java"/"Kotlin": Maven Central + GitHub stars + mvnrepository.com "Used By"
182
+ - "Unknown": not supported -- return empty result */
82
183
  static async info(stack, components) {
83
- /* FIXME: currently just limited technology stack support */
84
- if (stack !== "JavaScript" && stack !== "TypeScript")
184
+ if (stack === "JavaScript" || stack === "TypeScript") {
185
+ /* per package: kick off packument and downloads in parallel,
186
+ then stars as soon as the packument resolves; across packages
187
+ everything runs concurrently via Promise.all */
188
+ const results = await Promise.all(components.map(async (name) => {
189
+ const packumentPromise = Skills.fetchPackument(name);
190
+ const downloadsPromise = Skills.fetchDownloads(name);
191
+ const starsPromise = packumentPromise.then((p) => Skills.fetchStars(p.repository));
192
+ const [p, downloads, stars] = await Promise.all([
193
+ packumentPromise, downloadsPromise, starsPromise
194
+ ]);
195
+ const created = p.time.created ?? "";
196
+ const updated = p.version !== "" ? (p.time[p.version] ?? "") : "";
197
+ const rank = Skills.computeRank(downloads, stars, created, updated);
198
+ return {
199
+ name,
200
+ version: p.version,
201
+ created,
202
+ updated,
203
+ repository: p.repository,
204
+ stars,
205
+ downloads,
206
+ rank
207
+ };
208
+ }));
209
+ /* sort by rank in descending order (best first) */
210
+ results.sort((a, b) => b.rank - a.rank);
211
+ return results;
212
+ }
213
+ else if (stack === "Java" || stack === "Kotlin") {
214
+ /* per coordinate: kick off Maven Central info and mvnrepository
215
+ downloads in parallel, then stars as soon as the POM-derived
216
+ repository is known; across coordinates everything runs
217
+ concurrently via Promise.all */
218
+ const results = await Promise.all(components.map(async (name) => {
219
+ const infoPromise = Skills.fetchMavenInfo(name);
220
+ const downloadsPromise = Skills.fetchMavenDownloads(name);
221
+ const starsPromise = infoPromise.then((i) => Skills.fetchStars(i.repository));
222
+ const [i, downloads, stars] = await Promise.all([
223
+ infoPromise, downloadsPromise, starsPromise
224
+ ]);
225
+ const rank = Skills.computeRank(downloads, stars, i.created, i.updated);
226
+ return {
227
+ name,
228
+ version: i.version,
229
+ created: i.created,
230
+ updated: i.updated,
231
+ repository: i.repository,
232
+ stars,
233
+ downloads,
234
+ rank
235
+ };
236
+ }));
237
+ /* sort by rank in descending order (best first) */
238
+ results.sort((a, b) => b.rank - a.rank);
239
+ return results;
240
+ }
241
+ else
85
242
  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
243
  }
114
244
  /* compute composite rank score from weighted metrics:
115
245
  downloads x
116
246
  stars x
117
247
  ([lifespan =] (updated - created)) x
118
- ([recentness =] exp(-(now - updated) / halfLife)) */
248
+ ([recentness =] exp(-(now - updated) / halfLife))
249
+ `"N.A."` factors are treated as neutral `1` so that stacks for which
250
+ a particular metric is structurally unavailable (e.g. Maven Central
251
+ exposes no per-artifact download counts) can still be ranked by the
252
+ remaining metrics, instead of collapsing the entire product to zero. */
119
253
  static computeRank(downloads, stars, created, updated) {
120
- const d = typeof downloads === "number" ? downloads : 0;
121
- const s = typeof stars === "number" ? stars : 0;
254
+ const d = typeof downloads === "number" ? downloads : 1;
255
+ const s = typeof stars === "number" ? stars : 1;
122
256
  const cMs = created !== "" ? Date.parse(created) : NaN;
123
257
  const uMs = updated !== "" ? Date.parse(updated) : NaN;
124
258
  if (Number.isNaN(cMs) || Number.isNaN(uMs))
@@ -160,20 +294,26 @@ export class SkillsMCP {
160
294
  register(mcp) {
161
295
  mcp.registerTool("component_info", {
162
296
  title: "ASE component info",
163
- description: "Gather metadata for a list of NPM packages with maximum parallelism. " +
164
- "For each package, fetches the full registry packument via `pacote` " +
165
- "(in-process, no `npm view` subprocess), the GitHub `stargazers_count` " +
297
+ description: "Gather metadata for a list of packages with maximum parallelism, " +
298
+ "dispatching on the technology `stack`. For `JavaScript`/`TypeScript`, " +
299
+ "components are NPM package names: fetches the full registry packument via " +
300
+ "`pacote` (in-process, no `npm view` subprocess), the GitHub `stargazers_count` " +
166
301
  "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.",
302
+ "last-month downloads from `api.npmjs.org`. For `Java`/`Kotlin`, components " +
303
+ "are Maven coordinates of the form `groupId:artifactId`: fetches the latest " +
304
+ "version + earliest/latest release timestamps from the Maven Central Solr " +
305
+ "search endpoint, the SCM/project URL from the latest POM at `repo1.maven.org`, " +
306
+ "GitHub stars (if applicable), and the \"Used By\" count from `mvnrepository.com` " +
307
+ "as the `downloads` proxy. Returns a JSON `text` array of " +
308
+ "`{ name, version, created, updated, repository, stars, downloads, rank }` " +
309
+ "objects, sorted in descending order by `rank`. Failures of individual side " +
310
+ "calls are isolated and reported as `\"N.A.\"` or empty string so every entry " +
311
+ "has the full shape.",
172
312
  inputSchema: {
173
313
  stack: z.string()
174
314
  .describe("Technology stack: \"JavaScript\", \"TypeScript\", \"Java\", \"Kotlin\", or \"Unknown\""),
175
315
  components: z.array(z.string())
176
- .describe("List of package names to gather metadata for")
316
+ .describe("List of package names (NPM) or Maven coordinates `groupId:artifactId` (Java/Kotlin)")
177
317
  }
178
318
  }, async (args) => {
179
319
  try {
@@ -175,7 +175,7 @@ export default class StatuslineCommand {
175
175
  /* parse and validate the --tool option */
176
176
  parseTool(value) {
177
177
  if (value !== "claude" && value !== "copilot")
178
- throw new Error(`invalid --tool value: "${value}" (expected "claude" or "copilot")`);
178
+ throw new InvalidArgumentError(`invalid --tool value: "${value}" (expected "claude" or "copilot")`);
179
179
  return value;
180
180
  }
181
181
  /* register commands */
@@ -357,7 +357,7 @@ export default class StatuslineCommand {
357
357
  S: () => {
358
358
  const pct5h = data.rate_limits?.five_hour?.used_percentage;
359
359
  if (pct5h !== undefined)
360
- emit(`${prefix("⏲", "session")}${c.bold(`${pct5h.toFixed(1)}%`)}`);
360
+ emit(`${prefix("⏲", "session-usage")}${c.bold(`${pct5h.toFixed(1)}%`)}`);
361
361
  },
362
362
  D: () => {
363
363
  const until5h = data.rate_limits?.five_hour?.resets_at ?? "";
@@ -368,7 +368,7 @@ export default class StatuslineCommand {
368
368
  W: () => {
369
369
  const pctWk = data.rate_limits?.seven_day?.used_percentage;
370
370
  if (pctWk !== undefined)
371
- emit(`${prefix("⏲", "weekly")}${c.bold(`${pctWk.toFixed(1)}%`)}`);
371
+ emit(`${prefix("⏲", "weekly-usage")}${c.bold(`${pctWk.toFixed(1)}%`)}`);
372
372
  },
373
373
  Q: () => {
374
374
  const untilWk = data.rate_limits?.seven_day?.resets_at ?? "";
@@ -390,11 +390,11 @@ export default class StatuslineCommand {
390
390
  /* ==== VERSION CONTROL ==== */
391
391
  a: () => {
392
392
  const linesAdded = data.cost?.total_lines_added ?? 0;
393
- emit(`${prefix("⊕", "added")}${c.bold(linesAdded)}`);
393
+ emit(`${prefix("⊕", "added")}${c.bold(String(linesAdded))}`);
394
394
  },
395
395
  r: () => {
396
396
  const linesRemoved = data.cost?.total_lines_removed ?? 0;
397
- emit(`${prefix("⊖", "removed")}${c.bold(linesRemoved)}`);
397
+ emit(`${prefix("⊖", "removed")}${c.bold(String(linesRemoved))}`);
398
398
  },
399
399
  b: () => {
400
400
  const g = getGit();
@@ -435,6 +435,15 @@ export default class StatuslineCommand {
435
435
  }
436
436
  };
437
437
  /* walk each template line and render */
438
+ const closeSpan = () => {
439
+ if (span !== null) {
440
+ const wrapped = span.color === "default" ?
441
+ span.buf :
442
+ (c[span.color])(span.buf);
443
+ span = null;
444
+ appendOutput(wrapped);
445
+ }
446
+ };
438
447
  for (const line of tmpl) {
439
448
  let i = 0;
440
449
  while (i < line.length) {
@@ -443,15 +452,8 @@ export default class StatuslineCommand {
443
452
  if (ch === "<") {
444
453
  const m = line.slice(i).match(/^<(\/?)([a-z]+)>/);
445
454
  if (m !== null && COLORS.has(m[2])) {
446
- if (m[1] === "/") {
447
- if (span !== null) {
448
- const wrapped = span.color === "default" ?
449
- span.buf :
450
- (c[span.color])(span.buf);
451
- span = null;
452
- appendOutput(wrapped);
453
- }
454
- }
455
+ if (m[1] === "/")
456
+ closeSpan();
455
457
  else if (span === null)
456
458
  span = { color: m[2], buf: "" };
457
459
  i += m[0].length;
@@ -468,13 +470,7 @@ export default class StatuslineCommand {
468
470
  }
469
471
  }
470
472
  /* flush any unterminated span at end of line */
471
- if (span !== null) {
472
- const wrapped = span.color === "default" ?
473
- span.buf :
474
- (c[span.color])(span.buf);
475
- span = null;
476
- appendOutput(wrapped);
477
- }
473
+ closeSpan();
478
474
  out += "\n";
479
475
  col = 0;
480
476
  }
package/dst/ase-task.js CHANGED
@@ -69,6 +69,21 @@ export class Task {
69
69
  fs.rmSync(path.dirname(file), { recursive: true, force: true });
70
70
  return true;
71
71
  }
72
+ /* rename a task by moving the entire task home directory
73
+ <project>/.ase/task/<oldId>/ to <project>/.ase/task/<newId>/;
74
+ returns true on success, false if the source task does not exist;
75
+ throws if the target id already exists */
76
+ static rename(oldId, newId) {
77
+ const oldDir = path.dirname(Task.path(oldId));
78
+ const newDir = path.dirname(Task.path(newId));
79
+ if (!fs.existsSync(oldDir))
80
+ return false;
81
+ if (fs.existsSync(newDir))
82
+ throw new Error(`task: target id "${newId}" already exists`);
83
+ fs.mkdirSync(path.dirname(newDir), { recursive: true });
84
+ fs.renameSync(oldDir, newDir);
85
+ return true;
86
+ }
72
87
  /* list all persisted tasks in lexicographic id order; if verbose is true,
73
88
  each entry's `mtime` is set to the `plan.md` modification time formatted
74
89
  as "YYYY-MM-DD HH:MM", otherwise it is left undefined */
@@ -227,6 +242,20 @@ export default class TaskCommand {
227
242
  this.log.write("info", `task: no task "${id}" to remove`);
228
243
  process.exit(removed ? 0 : 1);
229
244
  });
245
+ /* register CLI sub-command "ase task rename" */
246
+ task
247
+ .command("rename")
248
+ .description("Rename a task from <old> to <new>")
249
+ .argument("<old>", "Old task identifier")
250
+ .argument("<new>", "New task identifier")
251
+ .action((oldId, newId) => {
252
+ const renamed = Task.rename(oldId, newId);
253
+ if (renamed)
254
+ this.log.write("info", `task: renamed "${oldId}" to "${newId}"`);
255
+ else
256
+ this.log.write("info", `task: no task "${oldId}" to rename`);
257
+ process.exit(renamed ? 0 : 1);
258
+ });
230
259
  /* register CLI sub-command "ase task purge" */
231
260
  task
232
261
  .command("purge")
@@ -289,7 +318,7 @@ export class TaskMCP {
289
318
  const verbose = args.verbose ?? false;
290
319
  const items = Task.list(verbose);
291
320
  const tasks = verbose ?
292
- items.map((item) => ({ id: item.id, mtime: item.mtime })) :
321
+ items.map((item) => ({ id: item.id, mtime: item.mtime ?? "" })) :
293
322
  items.map((item) => ({ id: item.id }));
294
323
  const result = { tasks };
295
324
  return {
@@ -381,6 +410,36 @@ export class TaskMCP {
381
410
  };
382
411
  }
383
412
  });
413
+ /* task rename */
414
+ mcp.registerTool("task_rename", {
415
+ title: "ASE task rename",
416
+ description: "Rename a previously persisted task from `old` to `new` by atomically moving the " +
417
+ "task home directory. Returns a status `text` indicating whether the rename succeeded. " +
418
+ "Fails with an error if the target id already exists.",
419
+ inputSchema: {
420
+ old: z.string()
421
+ .describe("old task identifier (allowed characters: A-Z, a-z, 0-9, '-')"),
422
+ new: z.string()
423
+ .describe("new task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
424
+ }
425
+ }, async (args) => {
426
+ try {
427
+ const renamed = Task.rename(args.old, args.new);
428
+ const msg = renamed ?
429
+ `task_rename: OK: renamed task "${args.old}" to "${args.new}"` :
430
+ "WARNING: task not found";
431
+ return {
432
+ content: [{ type: "text", text: msg }]
433
+ };
434
+ }
435
+ catch (err) {
436
+ const message = err instanceof Error ? err.message : String(err);
437
+ return {
438
+ isError: true,
439
+ content: [{ type: "text", text: `ERROR: ${message}` }]
440
+ };
441
+ }
442
+ });
384
443
  /* task id get/set */
385
444
  mcp.registerTool("task_id", {
386
445
  title: "ASE task id get/set",
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.46",
9
+ "version": "0.0.49",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.0.46",
3
+ "version": "0.0.49",
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.46",
3
+ "version": "0.0.49",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",