@rse/ase 0.9.6 → 0.9.8

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 (58) hide show
  1. package/dst/ase-getopt.js +11 -1
  2. package/dst/ase-markdown.js +235 -0
  3. package/dst/ase-service.js +2 -0
  4. package/dst/ase-task.js +22 -20
  5. package/package.json +6 -6
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/.github/plugin/plugin.json +1 -1
  8. package/plugin/agents/ase-docs-proofread.md +2 -2
  9. package/plugin/meta/ase-constitution.md +7 -0
  10. package/plugin/meta/ase-control.md +24 -3
  11. package/plugin/meta/ase-format-task.md +14 -14
  12. package/plugin/meta/ase-getopt.md +2 -1
  13. package/plugin/meta/ase-skill.md +28 -9
  14. package/plugin/package.json +3 -3
  15. package/plugin/skills/ase-arch-analyze/SKILL.md +88 -89
  16. package/plugin/skills/ase-arch-discover/SKILL.md +18 -9
  17. package/plugin/skills/ase-code-analyze/SKILL.md +6 -5
  18. package/plugin/skills/ase-code-craft/SKILL.md +47 -40
  19. package/plugin/skills/ase-code-craft/help.md +2 -2
  20. package/plugin/skills/ase-code-explain/SKILL.md +1 -1
  21. package/plugin/skills/ase-code-insight/SKILL.md +1 -1
  22. package/plugin/skills/ase-code-lint/SKILL.md +16 -8
  23. package/plugin/skills/ase-code-refactor/SKILL.md +47 -40
  24. package/plugin/skills/ase-code-refactor/help.md +2 -2
  25. package/plugin/skills/ase-code-resolve/SKILL.md +48 -40
  26. package/plugin/skills/ase-code-resolve/help.md +2 -2
  27. package/plugin/skills/ase-docs-distill/SKILL.md +1 -1
  28. package/plugin/skills/ase-docs-distill/help.md +3 -3
  29. package/plugin/skills/ase-docs-proofread/SKILL.md +22 -13
  30. package/plugin/skills/ase-meta-brainstorm/SKILL.md +25 -6
  31. package/plugin/skills/ase-meta-brainstorm/help.md +6 -10
  32. package/plugin/skills/ase-meta-diff/SKILL.md +5 -4
  33. package/plugin/skills/ase-meta-diff/help.md +10 -11
  34. package/plugin/skills/ase-meta-evaluate/SKILL.md +10 -9
  35. package/plugin/skills/ase-meta-quorum/SKILL.md +15 -5
  36. package/plugin/skills/ase-meta-review/SKILL.md +3 -3
  37. package/plugin/skills/ase-meta-review/help.md +3 -3
  38. package/plugin/skills/ase-meta-search/SKILL.md +9 -8
  39. package/plugin/skills/ase-meta-steelman/SKILL.md +1 -1
  40. package/plugin/skills/ase-meta-why/SKILL.md +16 -10
  41. package/plugin/skills/ase-task-condense/SKILL.md +32 -17
  42. package/plugin/skills/ase-task-condense/help.md +1 -1
  43. package/plugin/skills/ase-task-delete/SKILL.md +6 -3
  44. package/plugin/skills/ase-task-edit/SKILL.md +58 -36
  45. package/plugin/skills/ase-task-edit/help.md +3 -3
  46. package/plugin/skills/ase-task-grill/SKILL.md +59 -24
  47. package/plugin/skills/ase-task-id/SKILL.md +11 -2
  48. package/plugin/skills/ase-task-implement/SKILL.md +38 -17
  49. package/plugin/skills/ase-task-implement/help.md +1 -1
  50. package/plugin/skills/ase-task-list/SKILL.md +1 -1
  51. package/plugin/skills/ase-task-preflight/SKILL.md +42 -22
  52. package/plugin/skills/ase-task-preflight/help.md +1 -1
  53. package/plugin/skills/ase-task-reboot/SKILL.md +31 -22
  54. package/plugin/skills/ase-task-rename/SKILL.md +5 -3
  55. package/plugin/skills/ase-task-view/SKILL.md +19 -8
  56. package/plugin/skills/ase-task-view/help.md +24 -5
  57. package/dst/ase-bash.js +0 -618
  58. package/dst/ase-hello.js +0 -24
package/dst/ase-getopt.js CHANGED
@@ -19,7 +19,9 @@ export class GetoptMCP {
19
19
  "containing those remaining tokens (quotes preserved), and " +
20
20
  "`info` is a markdown rendering of the parsed options in the " +
21
21
  "form `key: **value**, key: **value**, ...` for printing at " +
22
- "the top of a skill.",
22
+ "the top of a skill. Options whose long name starts with " +
23
+ "`int-` are treated as internal and are excluded from both " +
24
+ "the usage help and the `info` rendering.",
23
25
  inputSchema: {
24
26
  name: z.string()
25
27
  .describe("Name of the caller (e.g. skill name), used in error messages"),
@@ -53,6 +55,7 @@ export class GetoptMCP {
53
55
  /* tokenize spec and add one option per token */
54
56
  const tokens = args.spec.split(/\s+/).filter((e) => e.length > 0);
55
57
  const re = /^--([A-Za-z][A-Za-z0-9-]*)(?:\|-([A-Za-z]))?(?:=(\((.*)\)(\.\.\.)?|.*))?$/;
58
+ const internals = new Set();
56
59
  for (const tok of tokens) {
57
60
  const m = re.exec(tok);
58
61
  if (m === null)
@@ -76,6 +79,12 @@ export class GetoptMCP {
76
79
  }
77
80
  else
78
81
  opt.default(false);
82
+ if (long !== undefined && long.startsWith("int-")) {
83
+ /* internal option: hide from usage help and remember
84
+ its camel-cased key for the info rendering below */
85
+ opt.hideHelp();
86
+ internals.add(long.replace(/-(.)/g, (_, c) => c.toUpperCase()));
87
+ }
79
88
  cmd.addOption(opt);
80
89
  }
81
90
  /* parse args */
@@ -145,6 +154,7 @@ export class GetoptMCP {
145
154
  /* build markdown info rendering of parsed options */
146
155
  const opts = cmd.opts();
147
156
  const info = Object.entries(opts)
157
+ .filter(([k]) => !internals.has(k))
148
158
  .map(([k, v]) => `${k}: **${shQuote([String(v)])}**`)
149
159
  .join(", ");
150
160
  /* build result */
@@ -0,0 +1,235 @@
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
+ /* the reusable functionality */
8
+ export class Markdown {
9
+ /* prepare Markdown for improved rendering by rewriting
10
+ unordered bullet paragraphs -- replacing the "-"/"*" bullet marker with
11
+ "◯" and splitting multi-line inline code spans into per-line single-line
12
+ spans (so each physical line carries its own closed backtick span).
13
+ Fenced code blocks (``` / ~~~) are detected line-wise and passed
14
+ through entirely verbatim, so neither the inline-span splitting nor the
15
+ bullet-marker rewriting ever touches their contents. */
16
+ static prepare(text) {
17
+ if (typeof text !== "string")
18
+ throw new Error("markdown: text must be a string");
19
+ /* segment the input line-wise into alternating non-fenced and
20
+ fenced regions: a fenced code block opens with a line whose first
21
+ non-whitespace content is a run of 3+ backticks or tildes and
22
+ closes with a matching-or-longer run of the same marker; fenced
23
+ regions are emitted verbatim while only non-fenced regions are
24
+ handed to the rewriting passes below */
25
+ const lines = text.split("\n");
26
+ let result = "";
27
+ let buf = "";
28
+ let inFence = false;
29
+ let fenceCh = "";
30
+ let fenceLen = 0;
31
+ const flush = () => {
32
+ if (buf !== "") {
33
+ result += Markdown.rewrite(buf);
34
+ buf = "";
35
+ }
36
+ };
37
+ for (let li = 0; li < lines.length; li++) {
38
+ const line = lines[li];
39
+ const nl = li < lines.length - 1 ? "\n" : "";
40
+ const m = line.match(/^(\s*)(`{3,}|~{3,})/);
41
+ if (!inFence && m) {
42
+ /* a fence-opening line: flush the pending non-fenced buffer,
43
+ enter fenced mode, and emit the opener verbatim */
44
+ flush();
45
+ inFence = true;
46
+ fenceCh = m[2][0];
47
+ fenceLen = m[2].length;
48
+ result += line + nl;
49
+ }
50
+ else if (inFence) {
51
+ /* inside a fenced block: emit verbatim and, on a matching
52
+ closing fence line, leave fenced mode */
53
+ result += line + nl;
54
+ const c = line.match(/^(\s*)(`{3,}|~{3,})\s*$/);
55
+ if (c && c[2][0] === fenceCh && c[2].length >= fenceLen)
56
+ inFence = false;
57
+ }
58
+ else
59
+ /* a regular non-fenced line: accumulate for the passes */
60
+ buf += line + nl;
61
+ }
62
+ flush();
63
+ return result;
64
+ }
65
+ /* apply the inline-span and bullet-marker rewriting passes to a chunk of
66
+ non-fenced Markdown text (see prepare() for fenced-block handling) */
67
+ static rewrite(text) {
68
+ /* PASS 1: rewrite single-backtick inline code spans that carry
69
+ backslash-escaped backticks (`\``) into CommonMark-correct spans. */
70
+ {
71
+ let pre = "";
72
+ let j = 0;
73
+ while (j < text.length) {
74
+ const ch = text[j];
75
+ if (ch !== "`") {
76
+ /* literal backslash-escaped backtick *outside* any span
77
+ is left verbatim for later scanning */
78
+ pre += ch;
79
+ j++;
80
+ continue;
81
+ }
82
+ /* scan an opening single-backtick span, capturing its raw
83
+ inner content up to the matching unescaped close backtick */
84
+ let inner = "";
85
+ let k = j + 1;
86
+ let closed = false;
87
+ let escaped = false;
88
+ while (k < text.length) {
89
+ const c = text[k];
90
+ if (c === "\\" && k + 1 < text.length && text[k + 1] === "`") {
91
+ inner += "\\`";
92
+ escaped = true;
93
+ k += 2;
94
+ continue;
95
+ }
96
+ if (c === "`") {
97
+ closed = true;
98
+ break;
99
+ }
100
+ inner += c;
101
+ k++;
102
+ }
103
+ if (!closed || !escaped) {
104
+ /* not an escaped-backtick span: emit the opening backtick
105
+ verbatim and continue scanning from just after it */
106
+ pre += "`";
107
+ j++;
108
+ continue;
109
+ }
110
+ /* un-escape inner `\`` into literal backticks, then choose a
111
+ fence longer than the longest internal backtick run */
112
+ const content = inner.replace(/\\`/g, "`");
113
+ let maxRun = 0;
114
+ let run = 0;
115
+ for (const c of content) {
116
+ if (c === "`") {
117
+ run++;
118
+ if (run > maxRun)
119
+ maxRun = run;
120
+ }
121
+ else
122
+ run = 0;
123
+ }
124
+ const fence = "`".repeat(maxRun + 1);
125
+ const pad = (content.startsWith("`") || content.endsWith("`")) ? " " : "";
126
+ pre += `${fence}${pad}${content}${pad}${fence}`;
127
+ j = k + 1;
128
+ }
129
+ text = pre;
130
+ }
131
+ /* PASS 2: split multi-line inline code spans by scanning the
132
+ entire text character-by-character while tracking whether we
133
+ are currently inside an active backtick (U+0060) span; on
134
+ every "<newline><whitespaces>" sequence encountered while
135
+ inside a span, close the span before the break and re-open
136
+ it after the indentation, so each physical line becomes its
137
+ own single-line span */
138
+ let out = "";
139
+ let fence = 0;
140
+ let i = 0;
141
+ while (i < text.length) {
142
+ const ch = text[i];
143
+ if (ch === "`") {
144
+ /* measure the full backtick run (a code-span delimiter is a
145
+ *run* of backticks; a span opened by N backticks is closed
146
+ only by a run of exactly N backticks) */
147
+ let n = 0;
148
+ while (i < text.length && text[i] === "`") {
149
+ n++;
150
+ i++;
151
+ }
152
+ if (fence === 0)
153
+ /* open a span of fence width n */
154
+ fence = n;
155
+ else if (n === fence)
156
+ /* close the active span */
157
+ fence = 0;
158
+ out += "`".repeat(n);
159
+ continue;
160
+ }
161
+ if (fence > 0 && (ch === "\r" || ch === "\n")) {
162
+ /* consume optional CR followed by mandatory LF */
163
+ let nl = "";
164
+ if (ch === "\r" && i + 1 < text.length && text[i + 1] === "\n") {
165
+ nl = "\r\n";
166
+ i += 2;
167
+ }
168
+ else if (ch === "\n") {
169
+ nl = "\n";
170
+ i++;
171
+ }
172
+ else {
173
+ /* bare CR: not a recognized newline, pass through */
174
+ out += ch;
175
+ i++;
176
+ continue;
177
+ }
178
+ /* consume the following run of SPACE/TAB whitespace */
179
+ let ws = "";
180
+ while (i < text.length && (text[i] === " " || text[i] === "\t")) {
181
+ ws += text[i];
182
+ i++;
183
+ }
184
+ /* close span before the break, re-open after indentation,
185
+ preserving the active fence width on both sides */
186
+ const bars = "`".repeat(fence);
187
+ out += `${bars}${nl}${ws}${bars}`;
188
+ continue;
189
+ }
190
+ out += ch;
191
+ i++;
192
+ }
193
+ /* PASS 3: replace the leading "-"/"*" marker of every unordered
194
+ bullet paragraph with "◯", preserving the leading indentation
195
+ and the following whitespace; operate line-by-line so only the
196
+ actual list markers (at a line start) are affected */
197
+ out = out
198
+ .split("\n")
199
+ .map((line) => line.replace(/^(\s*)[-*]([ \t]+)/, "$1◯$2"))
200
+ .join("\n");
201
+ return out;
202
+ }
203
+ }
204
+ /* MCP registration entry point */
205
+ export class MarkdownMCP {
206
+ register(mcp) {
207
+ mcp.registerTool("ase_markdown_prepare", {
208
+ title: "ASE markdown prepare",
209
+ description: "Prepare Markdown `text` for improved rendering by rewriting " +
210
+ "unordered bullet paragraphs: replace the `-`/`*` bullet markers with `◯` " +
211
+ "and split multi-line inline code spans into per-line single-line spans. " +
212
+ "Single-backtick spans containing backslash-escaped backticks are rewritten " +
213
+ "into CommonMark-correct spans with a widened backtick fence and literal inner " +
214
+ "backticks. Returns the prepared Markdown as `text`.",
215
+ inputSchema: {
216
+ text: z.string()
217
+ .describe("Markdown text to prepare for improved rendering")
218
+ }
219
+ }, async (args) => {
220
+ try {
221
+ const text = Markdown.prepare(args.text);
222
+ return {
223
+ content: [{ type: "text", text }]
224
+ };
225
+ }
226
+ catch (err) {
227
+ const message = err instanceof Error ? err.message : String(err);
228
+ return {
229
+ isError: true,
230
+ content: [{ type: "text", text: `ERROR: ${message}` }]
231
+ };
232
+ }
233
+ });
234
+ }
235
+ }
@@ -18,6 +18,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
18
18
  import { Config, configSchema, ConfigMCP } from "./ase-config.js";
19
19
  import { DiagramMCP } from "./ase-diagram.js";
20
20
  import { TaskMCP } from "./ase-task.js";
21
+ import { MarkdownMCP } from "./ase-markdown.js";
21
22
  import { ArtifactMCP } from "./ase-artifact.js";
22
23
  import { KVMCP } from "./ase-kv.js";
23
24
  import PersonaMCP from "./ase-persona.js";
@@ -237,6 +238,7 @@ export default class ServiceCommand {
237
238
  new ServiceMCP({ projectId: ctx.projectId, port: ctx.port, startTime }).register(mcp);
238
239
  new DiagramMCP().register(mcp);
239
240
  new TaskMCP(this.log).register(mcp);
241
+ new MarkdownMCP().register(mcp);
240
242
  new ArtifactMCP(this.log).register(mcp);
241
243
  new KVMCP().register(mcp);
242
244
  new PersonaMCP(this.log).register(mcp);
package/dst/ase-task.js CHANGED
@@ -11,6 +11,7 @@ import picomatch from "picomatch";
11
11
  import { isScalar } from "yaml";
12
12
  import { z } from "zod";
13
13
  import { Config, configSchema, parseScope } from "./ase-config.js";
14
+ import { Markdown } from "./ase-markdown.js";
14
15
  /* reusable functionality: persisted task plans under
15
16
  <project>/<basedir>/TASK-<id>.md (driven by the
16
17
  "project.artifact.task.{basedir,files}" configuration) */
@@ -19,8 +20,8 @@ export class Task {
19
20
  static validateId(id) {
20
21
  if (typeof id !== "string" || id.length === 0)
21
22
  throw new Error("task: id must be a non-empty string");
22
- if (!/^[A-Za-z0-9-]+$/.test(id))
23
- throw new Error("task: id must match [A-Za-z0-9-]+");
23
+ if (!/^[A-Za-z0-9_-]+$/.test(id))
24
+ throw new Error("task: id must match [A-Za-z0-9_-]+");
24
25
  }
25
26
  /* determine the project root (Git top-level if inside a Git
26
27
  working tree, otherwise the current working directory) */
@@ -75,7 +76,7 @@ export class Task {
75
76
  if (!fs.existsSync(dir))
76
77
  return false;
77
78
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
78
- if (!entry.isDirectory() || !/^[A-Za-z0-9-]+$/.test(entry.name))
79
+ if (!entry.isDirectory() || !/^[A-Za-z0-9_-]+$/.test(entry.name))
79
80
  continue;
80
81
  if (fs.existsSync(path.join(dir, entry.name, "plan.md")))
81
82
  return true;
@@ -148,7 +149,7 @@ export class Task {
148
149
  if (fs.existsSync(newFile))
149
150
  throw new Error(`task: target id "${newId}" already exists`);
150
151
  const text = fs.readFileSync(oldFile, "utf8");
151
- const updated = text.replace(/(^#\s+(?:✪\s+)?TASK\s+)[A-Za-z0-9-]+(\s*:)/m, `$1${newId}$2`);
152
+ const updated = text.replace(/(^#\s+TASK\s+)[A-Za-z0-9_-]+(\s*:)/m, `$1${newId}$2`);
152
153
  fs.mkdirSync(path.dirname(newFile), { recursive: true });
153
154
  fs.writeFileSync(newFile, updated, "utf8");
154
155
  fs.rmSync(oldFile, { force: true });
@@ -167,7 +168,7 @@ export class Task {
167
168
  const isMatch = picomatch(files, { dot: true });
168
169
  const out = [];
169
170
  for (const entry of fs.readdirSync(dir)) {
170
- const m = /^TASK-([A-Za-z0-9-]+)\.md$/.exec(entry);
171
+ const m = /^TASK-([A-Za-z0-9_-]+)\.md$/.exec(entry);
171
172
  if (m === null || !isMatch(entry))
172
173
  continue;
173
174
  const file = path.join(dir, entry);
@@ -193,7 +194,7 @@ export class Task {
193
194
  const cutoff = Date.now() - maxAgeMs;
194
195
  const removed = [];
195
196
  for (const entry of fs.readdirSync(dir)) {
196
- const m = /^TASK-([A-Za-z0-9-]+)\.md$/.exec(entry);
197
+ const m = /^TASK-([A-Za-z0-9_-]+)\.md$/.exec(entry);
197
198
  if (m === null || !isMatch(entry))
198
199
  continue;
199
200
  const file = path.join(dir, entry);
@@ -416,11 +417,12 @@ export class TaskMCP {
416
417
  "Returns the task as `text`; returns an empty string if no task exists for the `id`.",
417
418
  inputSchema: {
418
419
  id: z.string()
419
- .describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
420
+ .describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
420
421
  }
421
422
  }, async (args) => {
422
423
  try {
423
- const text = Task.load(this.log, args.id);
424
+ const raw = Task.load(this.log, args.id);
425
+ const text = Markdown.prepare(raw);
424
426
  return {
425
427
  content: [{ type: "text", text }]
426
428
  };
@@ -440,7 +442,7 @@ export class TaskMCP {
440
442
  "Overwrites any existing task for the same `id`.",
441
443
  inputSchema: {
442
444
  id: z.string()
443
- .describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')"),
445
+ .describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')"),
444
446
  text: z.string()
445
447
  .describe("text content of the task")
446
448
  }
@@ -448,7 +450,7 @@ export class TaskMCP {
448
450
  try {
449
451
  Task.save(this.log, args.id, args.text);
450
452
  return {
451
- content: [{ type: "text", text: `task_save: OK: saved task "${args.id}"` }]
453
+ content: [{ type: "text", text: `OK: saved task "${args.id}"` }]
452
454
  };
453
455
  }
454
456
  catch (err) {
@@ -466,14 +468,14 @@ export class TaskMCP {
466
468
  "Returns a status `text` indicating whether a task existed and was removed.",
467
469
  inputSchema: {
468
470
  id: z.string()
469
- .describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
471
+ .describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
470
472
  }
471
473
  }, async (args) => {
472
474
  try {
473
475
  const removed = Task.delete(this.log, args.id);
474
476
  const msg = removed ?
475
- `task_delete: OK: removed task "${args.id}"` :
476
- `task_delete: WARNING: no task "${args.id}" to remove`;
477
+ `OK: removed task "${args.id}"` :
478
+ `WARNING: no task "${args.id}" to remove`;
477
479
  return {
478
480
  content: [{ type: "text", text: msg }]
479
481
  };
@@ -494,16 +496,16 @@ export class TaskMCP {
494
496
  "Fails with an error if the target id already exists.",
495
497
  inputSchema: {
496
498
  old: z.string()
497
- .describe("old task identifier (allowed characters: A-Z, a-z, 0-9, '-')"),
499
+ .describe("old task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')"),
498
500
  new: z.string()
499
- .describe("new task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
501
+ .describe("new task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
500
502
  }
501
503
  }, async (args) => {
502
504
  try {
503
505
  const renamed = Task.rename(this.log, args.old, args.new);
504
506
  const msg = renamed ?
505
- `task_rename: OK: renamed task "${args.old}" to "${args.new}"` :
506
- `task_rename: WARNING: no task "${args.old}" to rename`;
507
+ `OK: renamed task "${args.old}" to "${args.new}"` :
508
+ `WARNING: no task "${args.old}" to rename`;
507
509
  return {
508
510
  content: [{ type: "text", text: msg }]
509
511
  };
@@ -524,16 +526,16 @@ export class TaskMCP {
524
526
  "otherwise it returns the current task `id` of the `session`.",
525
527
  inputSchema: {
526
528
  id: z.string().optional()
527
- .describe("task identifier to set (allowed characters: A-Z, a-z, 0-9, '-'); " +
529
+ .describe("task identifier to set (allowed characters: A-Z, a-z, 0-9, '_', '-'); " +
528
530
  "if omitted, the current task id is returned"),
529
531
  session: z.string()
530
- .describe("session identifier (allowed characters: A-Z, a-z, 0-9, '-')")
532
+ .describe("session identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
531
533
  }
532
534
  }, async (args) => {
533
535
  try {
534
536
  if (args.id !== undefined) {
535
537
  Task.setId(this.log, args.session, args.id);
536
- const msg = `task_id: OK: set agent.task to "${args.id}" ` +
538
+ const msg = `OK: set agent.task to "${args.id}" ` +
537
539
  `for session "${args.session}"`;
538
540
  return {
539
541
  content: [{ type: "text", text: msg }]
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.6",
9
+ "version": "0.9.8",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -18,8 +18,8 @@
18
18
  "devDependencies": {
19
19
  "eslint": "9.39.4",
20
20
  "@eslint/js": "9.39.4",
21
- "@typescript-eslint/parser": "8.60.1",
22
- "@typescript-eslint/eslint-plugin": "8.60.1",
21
+ "@typescript-eslint/parser": "8.61.0",
22
+ "@typescript-eslint/eslint-plugin": "8.61.0",
23
23
  "eslint-plugin-promise": "7.3.0",
24
24
  "eslint-plugin-import": "2.32.0",
25
25
  "neostandard": "0.13.0",
@@ -30,7 +30,7 @@
30
30
  "nodemon": "3.1.14",
31
31
  "shx": "0.4.0",
32
32
 
33
- "@types/node": "25.9.2",
33
+ "@types/node": "25.9.3",
34
34
  "@types/luxon": "3.7.1",
35
35
  "@types/which": "3.0.4",
36
36
  "@types/update-notifier": "6.0.8",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "commander": "15.0.0",
45
- "@dotenvx/dotenvx": "1.71.0",
45
+ "@dotenvx/dotenvx": "1.71.2",
46
46
  "yaml": "2.9.0",
47
47
  "valibot": "1.4.1",
48
48
  "execa": "9.6.1",
@@ -60,7 +60,7 @@
60
60
  "shell-quote": "1.8.4",
61
61
  "proper-lockfile": "4.1.2",
62
62
  "write-file-atomic": "8.0.0",
63
- "pacote": "21.5.0",
63
+ "pacote": "21.5.1",
64
64
  "ofetch": "1.5.1",
65
65
  "picomatch": "4.0.4"
66
66
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
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.6",
3
+ "version": "0.9.8",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -62,9 +62,9 @@ Workflow
62
62
  6. Set <context-after/> to exactly *up to two* lines of
63
63
  *unchanged* text content which occurs in the document
64
64
  directly *after* <old-text/>, i.e., the lines (<line/>
65
- + <n/> + 1) and (<line/> + <n/> + 2), where <n/> is the
65
+ + <n/>) and (<line/> + <n/> + 1), where <n/> is the
66
66
  number of lines in <old-text/>. Reduce to just one line
67
- (<line/> + <n/> + 1) if <old-text/> is the second-last
67
+ (<line/> + <n/>) if <old-text/> is the second-last
68
68
  line of the document. Set <context-after/> to empty if
69
69
  <old-text/> is the last line in the document.
70
70
 
@@ -16,6 +16,13 @@ you *MUST* once and immediately output the following <template/> now:
16
16
  ⧉ **ASE**: ☯ persona: **<ase-persona-style/>**
17
17
  </template>
18
18
 
19
+ In case your harness instructions indicate that the user is shown
20
+ only the *final* text message of each turn (e.g. *Claude Code* with
21
+ "focus mode" enabled), you *MUST* ensure this <template/> output (and
22
+ *every* <template/> output requested by ASE skills) lands in a final
23
+ text message (after the last tool call of a turn) instead of between
24
+ tool calls -- repeat it there if necessary.
25
+
19
26
  Prohibitions
20
27
  ------------
21
28
 
@@ -14,7 +14,7 @@ Control Flow Constructs
14
14
  Do not output anything.
15
15
 
16
16
  - *IMPORTANT*: You *MUST* honor the following control flow construct:
17
- <expand name="<define-name/>" [arg1="<expand-arg1/>" [arg2="<expand-arg2/>" [...]]]><expand-content/></expand>:
17
+ <expand name="<define-name/>" [arg1="<expand-arg1/>" [arg2="<expand-arg2/>" [...]]]>[<expand-content/>]</expand>:
18
18
 
19
19
  This specifies the *expansion* of previous <define/>. This
20
20
  construct is expanded to the <define-body/> of <define/> with
@@ -48,14 +48,35 @@ Control Flow Constructs
48
48
 
49
49
  This specifies a simple condition which is expanded to <if-body/>
50
50
  if <if-condition/> is met, or to empty string if <if-condition/> is
51
- *not* met. Do not output anything else.
51
+ *not* met. It can be optionally followed by one or more <elseif/>
52
+ constructs and/or one final <else/> construct. Do not output anything else.
53
+
54
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
55
+ <elseif condition="<elseif-condition/>"><elseif-body/></elseif>:
56
+
57
+ This specifies an *alternative condition* and has to directly
58
+ follow an <if/> or another <elseif/> construct. It is expanded
59
+ to <elseif-body/> if the conditions of all preceding <if/> and
60
+ <elseif/> constructs of the chain were *not* met and its own
61
+ <elseif-condition/> is met, or to the empty string otherwise.
62
+ Do not output anything else.
63
+
64
+ - *IMPORTANT*: You *MUST* honor the following control flow construct:
65
+ <else><else-body/></else>:
66
+
67
+ This specifies the *fallback alternative* and has to directly
68
+ follow an <if/> or <elseif/> construct. It is expanded to
69
+ <else-body/> if the conditions of all preceding <if/> and
70
+ <elseif/> constructs of the chain were *not* met, or to the empty
71
+ string otherwise. Do not output anything else.
52
72
 
53
73
  - *IMPORTANT*: You *MUST* honor the following control flow construct:
54
74
  <while condition="<while-condition/>"><while-body/></while>:
55
75
 
56
76
  This specifies a <while-body/> which is *repeated* as long as
57
77
  <while-condition/> is met. This construct is expanded to the
58
- repetition of <while-body/>. Do not output anything else.
78
+ repetition of <while-body/>. A <break/> in <while-body/> can stop
79
+ the repetition early. Do not output anything else.
59
80
 
60
81
  - *IMPORTANT*: You *MUST* honor the following control flow construct:
61
82
  <for items="<for-item/> [...]"><for-body/></for>:
@@ -6,28 +6,28 @@ Every *task* uses a strict and fixed format:
6
6
 
7
7
  <format>
8
8
 
9
- #TASK <task-id/>: <title/>
9
+ # TASK <task-id/>: <title/>
10
10
 
11
- Created: **<timestamp-created/>**
12
- Modified: **<timestamp-modified/>**
11
+ Created: <timestamp-created/>
12
+ Modified: <timestamp-modified/>
13
13
 
14
- ##CONTEXT
14
+ ## CONTEXT
15
15
 
16
- - **WHAT**: <summary-what/>
16
+ - **WHAT**: <summary-what/>
17
17
 
18
- - **WHY**: <summary-why/>
18
+ - **WHY**: <summary-why/>
19
19
 
20
- ##CHANGES
20
+ ## CHANGES
21
21
 
22
- - [...]
22
+ - [...]
23
23
 
24
- - [...]
24
+ - [...]
25
25
 
26
- ##VERIFICATION
26
+ ## VERIFICATION
27
27
 
28
- - [...]
28
+ - [...]
29
29
 
30
- - [...]
30
+ - [...]
31
31
 
32
32
  </format>
33
33
 
@@ -61,7 +61,7 @@ You *MUST* honor the following hints on this *task* format:
61
61
  - The <title/> is a short summary of the <summary-what/>, no longer than
62
62
  50 characters.
63
63
 
64
- - The sections `※ CHANGES` and `※ VERIFICATION` all are just a short
64
+ - The sections `## CHANGES` and `## VERIFICATION` all are just a short
65
65
  list of 1-5 bullet points. Each bullet point is formatted as
66
66
  `- **<aspect/>**: <specification/>` where <aspect/> indicates
67
67
  the aspect of the section and <specification/> is 1-3 sentences
@@ -69,6 +69,6 @@ You *MUST* honor the following hints on this *task* format:
69
69
  description of the aspect.
70
70
 
71
71
  - In all sections, break all lines with a newline character
72
- after about 120 characters per line for better subsequent
72
+ after about 100 characters per line for better subsequent
73
73
  manual editing.
74
74
 
@@ -28,7 +28,8 @@ set placeholders into the context as a side-effect.
28
28
  markdown rendering of the parsed options in the form `<longN/>:
29
29
  **<valueN/>**, [...]` (joined with `, `, with each value
30
30
  shell-quoted if value contains spaces or special characters, and
31
- excluding the `help` option).
31
+ excluding the `help` option and any *internal* option whose long
32
+ name starts with `int-`).
32
33
 
33
34
  Then silently *SKIP* only the following steps 3-6
34
35
  and proceed directly to step 7 to display the results.