@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.
- package/dst/ase-getopt.js +11 -1
- package/dst/ase-markdown.js +235 -0
- package/dst/ase-service.js +2 -0
- package/dst/ase-task.js +22 -20
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/agents/ase-docs-proofread.md +2 -2
- package/plugin/meta/ase-constitution.md +7 -0
- package/plugin/meta/ase-control.md +24 -3
- package/plugin/meta/ase-format-task.md +14 -14
- package/plugin/meta/ase-getopt.md +2 -1
- package/plugin/meta/ase-skill.md +28 -9
- package/plugin/package.json +3 -3
- package/plugin/skills/ase-arch-analyze/SKILL.md +88 -89
- package/plugin/skills/ase-arch-discover/SKILL.md +18 -9
- package/plugin/skills/ase-code-analyze/SKILL.md +6 -5
- package/plugin/skills/ase-code-craft/SKILL.md +47 -40
- package/plugin/skills/ase-code-craft/help.md +2 -2
- package/plugin/skills/ase-code-explain/SKILL.md +1 -1
- package/plugin/skills/ase-code-insight/SKILL.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +16 -8
- package/plugin/skills/ase-code-refactor/SKILL.md +47 -40
- package/plugin/skills/ase-code-refactor/help.md +2 -2
- package/plugin/skills/ase-code-resolve/SKILL.md +48 -40
- package/plugin/skills/ase-code-resolve/help.md +2 -2
- package/plugin/skills/ase-docs-distill/SKILL.md +1 -1
- package/plugin/skills/ase-docs-distill/help.md +3 -3
- package/plugin/skills/ase-docs-proofread/SKILL.md +22 -13
- package/plugin/skills/ase-meta-brainstorm/SKILL.md +25 -6
- package/plugin/skills/ase-meta-brainstorm/help.md +6 -10
- package/plugin/skills/ase-meta-diff/SKILL.md +5 -4
- package/plugin/skills/ase-meta-diff/help.md +10 -11
- package/plugin/skills/ase-meta-evaluate/SKILL.md +10 -9
- package/plugin/skills/ase-meta-quorum/SKILL.md +15 -5
- package/plugin/skills/ase-meta-review/SKILL.md +3 -3
- package/plugin/skills/ase-meta-review/help.md +3 -3
- package/plugin/skills/ase-meta-search/SKILL.md +9 -8
- package/plugin/skills/ase-meta-steelman/SKILL.md +1 -1
- package/plugin/skills/ase-meta-why/SKILL.md +16 -10
- package/plugin/skills/ase-task-condense/SKILL.md +32 -17
- package/plugin/skills/ase-task-condense/help.md +1 -1
- package/plugin/skills/ase-task-delete/SKILL.md +6 -3
- package/plugin/skills/ase-task-edit/SKILL.md +58 -36
- package/plugin/skills/ase-task-edit/help.md +3 -3
- package/plugin/skills/ase-task-grill/SKILL.md +59 -24
- package/plugin/skills/ase-task-id/SKILL.md +11 -2
- package/plugin/skills/ase-task-implement/SKILL.md +38 -17
- package/plugin/skills/ase-task-implement/help.md +1 -1
- package/plugin/skills/ase-task-list/SKILL.md +1 -1
- package/plugin/skills/ase-task-preflight/SKILL.md +42 -22
- package/plugin/skills/ase-task-preflight/help.md +1 -1
- package/plugin/skills/ase-task-reboot/SKILL.md +31 -22
- package/plugin/skills/ase-task-rename/SKILL.md +5 -3
- package/plugin/skills/ase-task-view/SKILL.md +19 -8
- package/plugin/skills/ase-task-view/help.md +24 -5
- package/dst/ase-bash.js +0 -618
- 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
|
+
}
|
package/dst/ase-service.js
CHANGED
|
@@ -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-
|
|
23
|
-
throw new Error("task: id must match [A-Za-z0-
|
|
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-
|
|
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+
|
|
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-
|
|
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-
|
|
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
|
|
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: `
|
|
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
|
-
`
|
|
476
|
-
`
|
|
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
|
-
`
|
|
506
|
-
`
|
|
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 = `
|
|
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.
|
|
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.
|
|
22
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
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.
|
|
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.
|
|
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.
|
|
63
|
+
"pacote": "21.5.1",
|
|
64
64
|
"ofetch": "1.5.1",
|
|
65
65
|
"picomatch": "4.0.4"
|
|
66
66
|
},
|
|
@@ -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/>
|
|
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/>
|
|
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/>" [...]]]
|
|
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.
|
|
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/>.
|
|
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
|
-
#
|
|
9
|
+
# TASK <task-id/>: <title/>
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
⎈ Created: <timestamp-created/>
|
|
12
|
+
⚙ Modified: <timestamp-modified/>
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## CONTEXT
|
|
15
15
|
|
|
16
|
-
-
|
|
16
|
+
- **WHAT**: <summary-what/>
|
|
17
17
|
|
|
18
|
-
-
|
|
18
|
+
- **WHY**: <summary-why/>
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## CHANGES
|
|
21
21
|
|
|
22
|
-
-
|
|
22
|
+
- [...]
|
|
23
23
|
|
|
24
|
-
-
|
|
24
|
+
- [...]
|
|
25
25
|
|
|
26
|
-
##
|
|
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
|
|
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
|
|
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.
|