@rse/ase 0.0.39 → 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 (37) 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/meta/ase-constitution.md +2 -1
  9. package/plugin/meta/ase-control.md +63 -0
  10. package/plugin/meta/ase-persona.md +1 -0
  11. package/plugin/meta/ase-skill.md +1 -62
  12. package/plugin/package.json +1 -1
  13. package/plugin/skills/ase-arch-analyze/SKILL.md +8 -6
  14. package/plugin/skills/ase-arch-discover/SKILL.md +45 -32
  15. package/plugin/skills/ase-code-analyze/SKILL.md +1 -1
  16. package/plugin/skills/ase-code-craft/SKILL.md +13 -9
  17. package/plugin/skills/ase-code-explain/SKILL.md +7 -5
  18. package/plugin/skills/ase-code-insight/SKILL.md +8 -6
  19. package/plugin/skills/ase-code-lint/SKILL.md +1 -1
  20. package/plugin/skills/ase-code-refactor/SKILL.md +13 -9
  21. package/plugin/skills/ase-code-resolve/SKILL.md +18 -10
  22. package/plugin/skills/ase-meta-changes/SKILL.md +1 -1
  23. package/plugin/skills/ase-meta-chat/SKILL.md +1 -1
  24. package/plugin/skills/ase-meta-commit/SKILL.md +1 -1
  25. package/plugin/skills/ase-meta-evaluate/SKILL.md +11 -8
  26. package/plugin/skills/ase-meta-persona/SKILL.md +1 -0
  27. package/plugin/skills/ase-meta-quorum/SKILL.md +1 -1
  28. package/plugin/skills/ase-meta-search/SKILL.md +1 -1
  29. package/plugin/skills/ase-meta-why/SKILL.md +1 -1
  30. package/plugin/skills/ase-task-delete/SKILL.md +1 -1
  31. package/plugin/skills/ase-task-edit/SKILL.md +6 -5
  32. package/plugin/skills/ase-task-id/SKILL.md +1 -1
  33. package/plugin/skills/ase-task-implement/SKILL.md +6 -5
  34. package/plugin/skills/ase-task-list/SKILL.md +1 -1
  35. package/plugin/skills/ase-task-preflight/SKILL.md +11 -9
  36. package/plugin/skills/ase-task-reboot/SKILL.md +6 -5
  37. package/plugin/skills/ase-task-view/SKILL.md +1 -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.39",
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.39",
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.39",
3
+ "version": "0.0.41",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -110,5 +110,6 @@ Tenets
110
110
  Persona
111
111
  -------
112
112
 
113
- @./ase-skill.md
113
+ @./ase-control.md
114
114
  @./ase-persona.md
115
+
@@ -0,0 +1,63 @@
1
+
2
+ Control Flow Constructs
3
+ -----------------------
4
+
5
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
6
+ <define name="<define-name/>"><define-body/></define>:
7
+
8
+ This specifies a *reusable definition* named <define-name/> and
9
+ an <define-body/> which can contain arbitrary information with
10
+ optional `<args/>` (or alternatively, individual `<arg1/>`,
11
+ `<arg2/>`, etc) and optional `<content/>` references from
12
+ subsequent <expand/> calls.
13
+ This construct is expanded into nothing.
14
+ Do not output anything.
15
+
16
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
17
+ <expand name="<define-name/>" [arg1="<expand-arg1/>" [arg2="<expand-arg2/>]" [...]]]><expand-content/></expand>:
18
+
19
+ This specifies the *expansion* of previous <define/>.
20
+ This construct is expanded into the <define-body/> of <define/>
21
+ with `<args/>` substituted with `<expand-arg1/> <expand-arg2/>
22
+ [...]`, `<arg1/>` substituted with <expand-arg1/>, and `<content/>`
23
+ substituted with <expand-content/>.
24
+ Do not output anything else.
25
+
26
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
27
+ <flow><flow-body/></flow>:
28
+
29
+ This specifies a *sequential flow* of <step/>s, which have
30
+ to be followed/executed in exactly the given order.
31
+ This construct is expanded to its <flow-body/>.
32
+ Do not output anything else.
33
+
34
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
35
+ <step id="<id/>"><step-body/></step>:
36
+
37
+ This specifies a distinct *single step* in a <flow/>.
38
+ This construct is expanded to its <step-body/>.
39
+ Do not output anything else.
40
+
41
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
42
+ <if condition="<if-condition/>"><if-body/></if>:
43
+
44
+ This specifies a simple condition which is expanded to <if-body/>
45
+ if <if-condition/> is met, or to empty string if <if-condition/> is
46
+ *not* met. Do not output anything else.
47
+
48
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
49
+ <while condition="<while-condition/>"><while-body/></while>:
50
+
51
+ This specifies a <while-body/> which is *repeated* as long as
52
+ <while-condition/> is met. This construct is expanded to the
53
+ repetition of <while-body/>. Do not output anything else.
54
+
55
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
56
+ <for items="<for-item/> [...]"><for-body/></for>:
57
+
58
+ This specifies a <for-body/> which is *repeated* for all
59
+ <for-item/>s and where `<item/>` is expanded with the current
60
+ <for-item/> in <for-body/>. This construct is expanded to the
61
+ repetition of <for-body/>. A <break/> in <for-body/> can stop the
62
+ repetition early. Do not output anything else.
63
+
@@ -61,3 +61,4 @@ Apply Persona
61
61
  - Apply ruleset "level2": <expand name="level2"/>
62
62
  - Apply ruleset "level3": <expand name="level3"/>
63
63
  </if>
64
+
@@ -90,68 +90,6 @@ Skill Output
90
90
  Skills that report severity MUST support `ACCEPTED`
91
91
  in addition to `LOW`, `MEDIUM`, and `HIGH`.
92
92
 
93
- Skill Control Flow
94
- ------------------
95
-
96
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
97
- <define name="<define-name/>"><define-body/></define>:
98
-
99
- This specifies a *reusable definition* named <define-name/> and
100
- an <define-body/> which can contain arbitrary information with
101
- optional `<args/>` (or alternatively, individual `<arg1/>`,
102
- `<arg2/>`, etc) and optional `<content/>` references from
103
- subsequent <expand/> calls.
104
- This construct is expanded into nothing.
105
- Do not output anything.
106
-
107
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
108
- <expand name="<define-name/>" [arg1="<expand-arg1/>" [arg2="<expand-arg2/>]" [...]]]><expand-content/></expand>:
109
-
110
- This specifies the *expansion* of previous <define/>.
111
- This construct is expanded into the <define-body/> of <define/>
112
- with `<args/>` substituted with `<expand-arg1/> <expand-arg2/>
113
- [...]`, `<arg1/>` substituted with <expand-arg1/>, and `<content/>`
114
- substituted with <expand-content/>.
115
- Do not output anything else.
116
-
117
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
118
- <flow><flow-body/></flow>:
119
-
120
- This specifies a *sequential flow* of <step/>s, which have
121
- to be followed/executed in exactly the given order.
122
- This construct is expanded to its <flow-body/>.
123
- Do not output anything else.
124
-
125
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
126
- <step id="<id/>"><step-body/></step>:
127
-
128
- This specifies a distinct *single step* in a <flow/>.
129
- This construct is expanded to its <step-body/>.
130
- Do not output anything else.
131
-
132
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
133
- <if condition="<if-condition/>"><if-body/></if>:
134
-
135
- This specifies a simple condition which is expanded to <if-body/>
136
- if <if-condition/> is met, or to empty string if <if-condition/> is
137
- *not* met. Do not output anything else.
138
-
139
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
140
- <while condition="<while-condition/>"><while-body/></while>:
141
-
142
- This specifies a <while-body/> which is *repeated* as long as
143
- <while-condition/> is met. This construct is expanded to the
144
- repetition of <while-body/>. Do not output anything else.
145
-
146
- - *IMPORTANT*: You *MUST* honor the following control flow construct:
147
- <for items="<for-item/> [...]"><for-body/></for>:
148
-
149
- This specifies a <for-body/> which is *repeated* for all
150
- <for-item/>s and where `<item/>` is expanded with the current
151
- <for-item/> in <for-body/>. This construct is expanded to the
152
- repetition of <for-body/>. A <break/> in <for-body/> can stop the
153
- repetition early. Do not output anything else.
154
-
155
93
  Skill Sequential Processing
156
94
  ---------------------------
157
95
 
@@ -237,3 +175,4 @@ Skill Identification
237
175
  <template>
238
176
  ⧉ **ASE**: ◎ objective: **<objective/>**
239
177
  </template>
178
+
@@ -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.39",
9
+ "version": "0.0.41",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",