@rse/ase 0.9.43 → 0.9.45
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-artifact.js +28 -24
- package/dst/ase-config.js +27 -36
- package/dst/ase-diagram.js +15 -35
- package/dst/ase-getopt.js +10 -15
- package/dst/ase-hook.js +29 -27
- package/dst/ase-kv.js +24 -29
- package/dst/ase-log.js +10 -3
- package/dst/ase-markdown.js +135 -102
- package/dst/ase-mcp.js +3 -10
- package/dst/ase-meta.js +6 -4
- package/dst/ase-persona.js +7 -11
- package/dst/ase-service.js +28 -28
- package/dst/ase-setup.js +65 -28
- package/dst/ase-skills.js +2 -2
- package/dst/ase-statusline.js +26 -34
- package/dst/ase-task.js +60 -63
- package/dst/ase-version.js +2 -1
- package/dst/ase.js +7 -11
- package/package.json +12 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +2 -2
- package/plugin/agents/ase-code-lint.md +15 -4
- package/plugin/agents/ase-docs-proofread.md +9 -1
- package/plugin/agents/ase-meta-chat.md +19 -13
- package/plugin/agents/ase-meta-diagram.md +1 -1
- package/plugin/agents/ase-meta-search.md +1 -2
- package/plugin/etc/eslint.mjs +0 -2
- package/plugin/meta/ase-common-task.md +145 -0
- package/plugin/meta/ase-constitution.md +5 -2
- package/plugin/meta/ase-control.md +1 -1
- package/plugin/meta/ase-dialog.md +7 -7
- package/plugin/meta/ase-format-arch.md +12 -12
- package/plugin/meta/ase-format-spec.md +23 -23
- package/plugin/meta/ase-format-task.md +9 -11
- package/plugin/meta/ase-getopt.md +1 -1
- package/plugin/meta/ase-persona.md +3 -3
- package/plugin/meta/ase-skill.md +3 -3
- package/plugin/meta/ase-tenets.md +4 -4
- package/plugin/package.json +8 -8
- package/plugin/skills/ase-arch-analyze/SKILL.md +2 -3
- package/plugin/skills/ase-arch-analyze/help.md +2 -2
- package/plugin/skills/ase-arch-discover/SKILL.md +2 -2
- package/plugin/skills/ase-arch-discover/help.md +3 -2
- package/plugin/skills/ase-code-analyze/SKILL.md +0 -1
- package/plugin/skills/ase-code-craft/SKILL.md +11 -11
- package/plugin/skills/ase-code-lint/SKILL.md +31 -13
- package/plugin/skills/ase-code-refactor/SKILL.md +13 -13
- package/plugin/skills/ase-code-refactor/help.md +1 -1
- package/plugin/skills/ase-code-resolve/SKILL.md +10 -10
- package/plugin/skills/ase-code-resolve/help.md +1 -1
- package/plugin/skills/ase-docs-distill/SKILL.md +6 -6
- package/plugin/skills/ase-docs-distill/help.md +6 -4
- package/plugin/skills/ase-docs-proofread/SKILL.md +23 -8
- package/plugin/skills/ase-meta-brainstorm/help.md +1 -1
- package/plugin/skills/ase-meta-changelog/SKILL.md +9 -5
- package/plugin/skills/ase-meta-commit/SKILL.md +8 -6
- package/plugin/skills/ase-meta-compat/SKILL.md +14 -5
- package/plugin/skills/ase-meta-diaboli/SKILL.md +6 -6
- package/plugin/skills/ase-meta-diff/SKILL.md +10 -10
- package/plugin/skills/ase-meta-evaluate/SKILL.md +6 -9
- package/plugin/skills/ase-meta-persona/SKILL.md +1 -1
- package/plugin/skills/ase-meta-quorum/SKILL.md +43 -12
- package/plugin/skills/ase-meta-search/SKILL.md +1 -1
- package/plugin/skills/ase-meta-search/help.md +4 -2
- package/plugin/skills/ase-meta-steelman/SKILL.md +5 -5
- package/plugin/skills/ase-meta-steelman/help.md +5 -3
- package/plugin/skills/ase-meta-why/SKILL.md +2 -2
- package/plugin/skills/ase-meta-why/help.md +3 -1
- package/plugin/skills/ase-sync-export/SKILL.md +20 -17
- package/plugin/skills/ase-sync-import/SKILL.md +20 -16
- package/plugin/skills/ase-sync-import/help.md +2 -2
- package/plugin/skills/ase-sync-reconcile/SKILL.md +10 -10
- package/plugin/skills/ase-sync-reconcile/help.md +1 -1
- package/plugin/skills/ase-task-condense/SKILL.md +39 -85
- package/plugin/skills/ase-task-delete/SKILL.md +1 -1
- package/plugin/skills/ase-task-edit/SKILL.md +43 -51
- package/plugin/skills/ase-task-grill/SKILL.md +11 -13
- package/plugin/skills/ase-task-id/SKILL.md +7 -3
- package/plugin/skills/ase-task-implement/SKILL.md +18 -111
- package/plugin/skills/ase-task-implement/help.md +2 -2
- package/plugin/skills/ase-task-list/SKILL.md +3 -0
- package/plugin/skills/ase-task-preflight/SKILL.md +22 -115
- package/plugin/skills/ase-task-reboot/SKILL.md +39 -71
- package/plugin/skills/ase-task-reboot/help.md +2 -1
- package/plugin/skills/ase-task-view/SKILL.md +7 -7
package/dst/ase-kv.js
CHANGED
|
@@ -95,7 +95,7 @@ export class KVMCP {
|
|
|
95
95
|
}
|
|
96
96
|
catch (err) {
|
|
97
97
|
const message = err instanceof Error ? err.message : String(err);
|
|
98
|
-
return { isError: true, content: [{ type: "text", text: `
|
|
98
|
+
return { isError: true, content: [{ type: "text", text: `ERROR: ${message}` }] };
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
/* key/value set */
|
|
@@ -107,17 +107,17 @@ export class KVMCP {
|
|
|
107
107
|
inputSchema: {
|
|
108
108
|
key: z.string()
|
|
109
109
|
.describe("key identifier (non-empty, no whitespace-only, no control characters, up to 1024 characters)"),
|
|
110
|
-
val: z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.
|
|
110
|
+
val: z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.unknown()), z.record(z.string(), z.unknown())])
|
|
111
111
|
.describe("arbitrary JSON-compatible value to store under `key`")
|
|
112
112
|
}
|
|
113
113
|
}, async (args) => {
|
|
114
114
|
try {
|
|
115
115
|
KV.set(args.key, args.val);
|
|
116
|
-
return { content: [{ type: "text", text: `
|
|
116
|
+
return { content: [{ type: "text", text: `OK: stored key "${args.key}"` }] };
|
|
117
117
|
}
|
|
118
118
|
catch (err) {
|
|
119
119
|
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
-
return { isError: true, content: [{ type: "text", text: `
|
|
120
|
+
return { isError: true, content: [{ type: "text", text: `ERROR: ${message}` }] };
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
123
|
/* key/value clear */
|
|
@@ -134,11 +134,11 @@ export class KVMCP {
|
|
|
134
134
|
}, async (args) => {
|
|
135
135
|
try {
|
|
136
136
|
const n = KV.clear(args.prefix);
|
|
137
|
-
return { content: [{ type: "text", text: `
|
|
137
|
+
return { content: [{ type: "text", text: `OK: removed ${n} key(s)` }] };
|
|
138
138
|
}
|
|
139
139
|
catch (err) {
|
|
140
140
|
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
-
return { isError: true, content: [{ type: "text", text: `
|
|
141
|
+
return { isError: true, content: [{ type: "text", text: `ERROR: ${message}` }] };
|
|
142
142
|
}
|
|
143
143
|
});
|
|
144
144
|
/* key/value delete */
|
|
@@ -154,13 +154,13 @@ export class KVMCP {
|
|
|
154
154
|
try {
|
|
155
155
|
const removed = KV.delete(args.key);
|
|
156
156
|
const msg = removed ?
|
|
157
|
-
`
|
|
158
|
-
`
|
|
157
|
+
`OK: removed key "${args.key}"` :
|
|
158
|
+
`WARNING: no key "${args.key}" to remove`;
|
|
159
159
|
return { content: [{ type: "text", text: msg }] };
|
|
160
160
|
}
|
|
161
161
|
catch (err) {
|
|
162
162
|
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
-
return { isError: true, content: [{ type: "text", text: `
|
|
163
|
+
return { isError: true, content: [{ type: "text", text: `ERROR: ${message}` }] };
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
166
|
/* key/value batch */
|
|
@@ -174,17 +174,17 @@ export class KVMCP {
|
|
|
174
174
|
"first per-command error (remaining commands are skipped); otherwise per-command errors " +
|
|
175
175
|
"are recorded and execution continues. " +
|
|
176
176
|
"Returns a single `text` payload containing a JSON array of per-command result strings " +
|
|
177
|
-
"
|
|
177
|
+
"positionally matching the `commands` array. " +
|
|
178
178
|
"On transactional rollback, prior per-command result strings are rewritten to " +
|
|
179
|
-
"`
|
|
180
|
-
"final entry remains `
|
|
179
|
+
"`ROLLED-BACK` to truthfully reflect the post-rollback state, and the " +
|
|
180
|
+
"final entry remains `ERROR: <message>`.",
|
|
181
181
|
inputSchema: {
|
|
182
182
|
commands: z.array(z.object({
|
|
183
183
|
command: z.enum(["clear", "set", "get", "delete"])
|
|
184
184
|
.describe("the KV sub-command to execute"),
|
|
185
185
|
key: z.string().optional()
|
|
186
186
|
.describe("key identifier (required for `set`/`get`/`delete`)"),
|
|
187
|
-
val: z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.
|
|
187
|
+
val: z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.unknown()), z.record(z.string(), z.unknown())]).optional()
|
|
188
188
|
.describe("value to store (required for `set`)"),
|
|
189
189
|
prefix: z.string().optional()
|
|
190
190
|
.describe("if given for `clear`, only remove keys starting with this prefix (otherwise remove all keys)")
|
|
@@ -195,44 +195,39 @@ export class KVMCP {
|
|
|
195
195
|
}
|
|
196
196
|
}, async (args) => {
|
|
197
197
|
const results = [];
|
|
198
|
-
const cmdNames = [];
|
|
199
198
|
const tx = args.transactional === true;
|
|
200
199
|
const snapshot = tx ? KV.snapshot() : null;
|
|
201
200
|
for (const c of args.commands) {
|
|
202
201
|
try {
|
|
203
202
|
if (c.command === "clear") {
|
|
204
203
|
const n = KV.clear(c.prefix);
|
|
205
|
-
results.push(`
|
|
206
|
-
cmdNames.push("clear");
|
|
204
|
+
results.push(`OK: removed ${n} key(s)`);
|
|
207
205
|
}
|
|
208
206
|
else if (c.command === "set") {
|
|
209
207
|
if (c.key === undefined)
|
|
210
|
-
throw new Error("
|
|
208
|
+
throw new Error("missing `key`");
|
|
211
209
|
if (c.val === undefined)
|
|
212
|
-
throw new Error("
|
|
210
|
+
throw new Error("missing `val`");
|
|
213
211
|
KV.set(c.key, c.val);
|
|
214
|
-
results.push(`
|
|
215
|
-
cmdNames.push("set");
|
|
212
|
+
results.push(`OK: stored key "${c.key}"`);
|
|
216
213
|
}
|
|
217
214
|
else if (c.command === "get") {
|
|
218
215
|
if (c.key === undefined)
|
|
219
|
-
throw new Error("
|
|
216
|
+
throw new Error("missing `key`");
|
|
220
217
|
if (!KV.has(c.key))
|
|
221
218
|
results.push("");
|
|
222
219
|
else {
|
|
223
220
|
const val = KV.get(c.key);
|
|
224
221
|
results.push(val === undefined ? "" : JSON.stringify(val));
|
|
225
222
|
}
|
|
226
|
-
cmdNames.push("get");
|
|
227
223
|
}
|
|
228
224
|
else if (c.command === "delete") {
|
|
229
225
|
if (c.key === undefined)
|
|
230
|
-
throw new Error("
|
|
226
|
+
throw new Error("missing `key`");
|
|
231
227
|
const removed = KV.delete(c.key);
|
|
232
228
|
results.push(removed ?
|
|
233
|
-
`
|
|
234
|
-
`
|
|
235
|
-
cmdNames.push("delete");
|
|
229
|
+
`OK: removed key "${c.key}"` :
|
|
230
|
+
`WARNING: no key "${c.key}" to remove`);
|
|
236
231
|
}
|
|
237
232
|
}
|
|
238
233
|
catch (err) {
|
|
@@ -241,11 +236,11 @@ export class KVMCP {
|
|
|
241
236
|
if (snapshot !== null)
|
|
242
237
|
KV.restore(snapshot);
|
|
243
238
|
for (let i = 0; i < results.length; i++)
|
|
244
|
-
results[i] =
|
|
245
|
-
results.push(`
|
|
239
|
+
results[i] = "ROLLED-BACK";
|
|
240
|
+
results.push(`ERROR: ${message}`);
|
|
246
241
|
return { isError: true, content: [{ type: "text", text: JSON.stringify(results) }] };
|
|
247
242
|
}
|
|
248
|
-
results.push(`
|
|
243
|
+
results.push(`ERROR: ${message}`);
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
246
|
return { content: [{ type: "text", text: JSON.stringify(results) }] };
|
package/dst/ase-log.js
CHANGED
|
@@ -30,7 +30,14 @@ export default class Log {
|
|
|
30
30
|
throw new RangeError(`invalid log level "${this._logLevel}" (expected one of: ${levels.map((l) => l.name).join(", ")})`);
|
|
31
31
|
this.logLevelIdx = idx;
|
|
32
32
|
if (this._logFile !== "-")
|
|
33
|
-
this.stream =
|
|
33
|
+
this.stream = this.openStream(this._logFile);
|
|
34
|
+
}
|
|
35
|
+
openStream(file) {
|
|
36
|
+
const stream = fs.createWriteStream(file, { flags: "a", encoding: "utf8" });
|
|
37
|
+
stream.on("error", (err) => {
|
|
38
|
+
process.stderr.write(`${this._program}: ERROR: cannot write log file "${file}": ${err.message}\n`);
|
|
39
|
+
});
|
|
40
|
+
return stream;
|
|
34
41
|
}
|
|
35
42
|
logLevel(level) {
|
|
36
43
|
const idx = levels.findIndex((l) => l.name === level);
|
|
@@ -48,14 +55,14 @@ export default class Log {
|
|
|
48
55
|
this.stream = null;
|
|
49
56
|
}
|
|
50
57
|
if (file !== "-")
|
|
51
|
-
this.stream =
|
|
58
|
+
this.stream = this.openStream(file);
|
|
52
59
|
}
|
|
53
60
|
write(level, msg) {
|
|
54
61
|
const idx = levels.findIndex((l) => l.name === level);
|
|
55
62
|
if (idx !== -1 && idx <= this.logLevelIdx) {
|
|
56
63
|
const timestamp = DateTime.now().toFormat("yyyy-LL-dd HH:mm:ss.SSS");
|
|
57
64
|
let line = `${this._program}: [${timestamp}]: `;
|
|
58
|
-
if (this._logFile === "-" && process.
|
|
65
|
+
if (this._logFile === "-" && process.stderr.isTTY)
|
|
59
66
|
line += `${levels[idx].style("[" + levels[idx].name.toUpperCase() + "]")}`;
|
|
60
67
|
else
|
|
61
68
|
line += `[${levels[idx].name.toUpperCase()}]`;
|
package/dst/ase-markdown.js
CHANGED
|
@@ -37,22 +37,22 @@ export class Markdown {
|
|
|
37
37
|
for (let li = 0; li < lines.length; li++) {
|
|
38
38
|
const line = lines[li];
|
|
39
39
|
const nl = li < lines.length - 1 ? "\n" : "";
|
|
40
|
-
const m = line.match(
|
|
40
|
+
const m = line.match(/^\s*(`{3,}|~{3,})/);
|
|
41
41
|
if (!inFence && m) {
|
|
42
42
|
/* a fence-opening line: flush the pending non-fenced buffer,
|
|
43
43
|
enter fenced mode, and emit the opener verbatim */
|
|
44
44
|
flush();
|
|
45
45
|
inFence = true;
|
|
46
|
-
fenceCh = m[
|
|
47
|
-
fenceLen = m[
|
|
46
|
+
fenceCh = m[1][0];
|
|
47
|
+
fenceLen = m[1].length;
|
|
48
48
|
result += line + nl;
|
|
49
49
|
}
|
|
50
50
|
else if (inFence) {
|
|
51
51
|
/* inside a fenced block: emit verbatim and, on a matching
|
|
52
52
|
closing fence line, leave fenced mode */
|
|
53
53
|
result += line + nl;
|
|
54
|
-
const c = line.match(
|
|
55
|
-
if (c && c[
|
|
54
|
+
const c = line.match(/^\s*(`{3,}|~{3,})\s*$/);
|
|
55
|
+
if (c && c[1][0] === fenceCh && c[1].length >= fenceLen)
|
|
56
56
|
inFence = false;
|
|
57
57
|
}
|
|
58
58
|
else
|
|
@@ -62,100 +62,104 @@ export class Markdown {
|
|
|
62
62
|
flush();
|
|
63
63
|
return result;
|
|
64
64
|
}
|
|
65
|
-
/*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
65
|
+
/* measure the length of the backtick run starting at offset pos */
|
|
66
|
+
static runLength(text, pos) {
|
|
67
|
+
let n = 0;
|
|
68
|
+
while (pos + n < text.length && text[pos + n] === "`")
|
|
69
|
+
n++;
|
|
70
|
+
return n;
|
|
71
|
+
}
|
|
72
|
+
/* PASS 1 of rewrite(): rewrite inline code spans that carry
|
|
73
|
+
backslash-escaped backticks (`\``) into CommonMark-correct spans */
|
|
74
|
+
static rewriteEscapedSpans(text) {
|
|
75
|
+
let pre = "";
|
|
76
|
+
let j = 0;
|
|
77
|
+
while (j < text.length) {
|
|
78
|
+
const ch = text[j];
|
|
79
|
+
if (ch !== "`") {
|
|
80
|
+
/* literal backslash-escaped backtick *outside* any span
|
|
81
|
+
is left verbatim for later scanning */
|
|
82
|
+
pre += ch;
|
|
83
|
+
j++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
/* measure the full opening backtick run: a code-span
|
|
87
|
+
delimiter is a *run* of backticks and a span opened by a
|
|
88
|
+
run of N backticks is closed only by a run of exactly N
|
|
89
|
+
backticks, so the opening-run length determines what we
|
|
90
|
+
scan for as the closing delimiter */
|
|
91
|
+
const open = Markdown.runLength(text, j);
|
|
92
|
+
/* scan the opening backtick-run span, capturing its raw
|
|
93
|
+
inner content up to the matching unescaped closing run of
|
|
94
|
+
exactly `open` backticks */
|
|
95
|
+
let inner = "";
|
|
96
|
+
let k = j + open;
|
|
97
|
+
let closed = false;
|
|
98
|
+
let escaped = false;
|
|
99
|
+
while (k < text.length) {
|
|
100
|
+
const c = text[k];
|
|
101
|
+
if (c === "\\" && k + 1 < text.length && text[k + 1] === "`") {
|
|
102
|
+
inner += "\\`";
|
|
103
|
+
escaped = true;
|
|
104
|
+
k += 2;
|
|
80
105
|
continue;
|
|
81
106
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
inner content up to the matching unescaped closing run of
|
|
92
|
-
exactly `open` backticks */
|
|
93
|
-
let inner = "";
|
|
94
|
-
let k = j + open;
|
|
95
|
-
let closed = false;
|
|
96
|
-
let escaped = false;
|
|
97
|
-
while (k < text.length) {
|
|
98
|
-
const c = text[k];
|
|
99
|
-
if (c === "\\" && k + 1 < text.length && text[k + 1] === "`") {
|
|
100
|
-
inner += "\\`";
|
|
101
|
-
escaped = true;
|
|
102
|
-
k += 2;
|
|
103
|
-
continue;
|
|
107
|
+
if (c === "`") {
|
|
108
|
+
/* measure this backtick run and treat it as the
|
|
109
|
+
closing delimiter only if it matches the opening
|
|
110
|
+
run length exactly; a shorter or longer run is
|
|
111
|
+
literal content of the span */
|
|
112
|
+
const runLen = Markdown.runLength(text, k);
|
|
113
|
+
if (runLen === open) {
|
|
114
|
+
closed = true;
|
|
115
|
+
break;
|
|
104
116
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
closing delimiter only if it matches the opening
|
|
108
|
-
run length exactly; a shorter or longer run is
|
|
109
|
-
literal content of the span */
|
|
110
|
-
let runLen = 0;
|
|
111
|
-
while (k + runLen < text.length && text[k + runLen] === "`")
|
|
112
|
-
runLen++;
|
|
113
|
-
if (runLen === open) {
|
|
114
|
-
closed = true;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
inner += "`".repeat(runLen);
|
|
118
|
-
k += runLen;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
inner += c;
|
|
122
|
-
k++;
|
|
123
|
-
}
|
|
124
|
-
if (!closed || !escaped) {
|
|
125
|
-
/* not an escaped-backtick span: emit the opening run
|
|
126
|
-
verbatim and continue scanning from just after it */
|
|
127
|
-
pre += "`".repeat(open);
|
|
128
|
-
j += open;
|
|
117
|
+
inner += "`".repeat(runLen);
|
|
118
|
+
k += runLen;
|
|
129
119
|
continue;
|
|
130
120
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
121
|
+
if (c === "\n" && /^[ \t]*\r?(?:\n|$)/.test(text.slice(k + 1)))
|
|
122
|
+
/* a code span never crosses a paragraph break */
|
|
123
|
+
break;
|
|
124
|
+
inner += c;
|
|
125
|
+
k++;
|
|
126
|
+
}
|
|
127
|
+
if (!closed || !escaped) {
|
|
128
|
+
/* not an escaped-backtick span: emit the opening run
|
|
129
|
+
verbatim and continue scanning from just after it */
|
|
130
|
+
pre += "`".repeat(open);
|
|
131
|
+
j += open;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
/* un-escape inner `\`` into literal backticks, then choose a
|
|
135
|
+
fence longer than the longest internal backtick run */
|
|
136
|
+
const content = inner.replace(/\\`/g, "`");
|
|
137
|
+
let maxRun = 0;
|
|
138
|
+
let run = 0;
|
|
139
|
+
for (const c of content) {
|
|
140
|
+
if (c === "`") {
|
|
141
|
+
run++;
|
|
142
|
+
if (run > maxRun)
|
|
143
|
+
maxRun = run;
|
|
144
144
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
pre += `${fence}${pad}${content}${pad}${fence}`;
|
|
148
|
-
j = k + open;
|
|
145
|
+
else
|
|
146
|
+
run = 0;
|
|
149
147
|
}
|
|
150
|
-
|
|
148
|
+
const fence = "`".repeat(maxRun + 1);
|
|
149
|
+
const pad = (content.startsWith("`") || content.endsWith("`")) ? " " : "";
|
|
150
|
+
pre += `${fence}${pad}${content}${pad}${fence}`;
|
|
151
|
+
j = k + open;
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
return pre;
|
|
154
|
+
}
|
|
155
|
+
/* PASS 2 of rewrite(): split multi-line inline code spans by scanning
|
|
156
|
+
the entire text character-by-character while tracking whether we
|
|
157
|
+
are currently inside an active backtick (U+0060) span; on
|
|
158
|
+
every "<newline><whitespaces>" sequence encountered while
|
|
159
|
+
inside a span, close the span before the break and re-open
|
|
160
|
+
it after the indentation, so each physical line becomes its
|
|
161
|
+
own single-line span */
|
|
162
|
+
static splitMultiLineSpans(text) {
|
|
159
163
|
let out = "";
|
|
160
164
|
let fence = 0;
|
|
161
165
|
let i = 0;
|
|
@@ -165,14 +169,31 @@ export class Markdown {
|
|
|
165
169
|
/* measure the full backtick run (a code-span delimiter is a
|
|
166
170
|
*run* of backticks; a span opened by N backticks is closed
|
|
167
171
|
only by a run of exactly N backticks) */
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
const n = Markdown.runLength(text, i);
|
|
173
|
+
i += n;
|
|
174
|
+
if (fence === 0) {
|
|
175
|
+
/* open a span of fence width n, but only if a matching
|
|
176
|
+
closing run of exactly n backticks exists ahead (an
|
|
177
|
+
unmatched run is literal text per CommonMark and must
|
|
178
|
+
not trigger the line-splitting below) */
|
|
179
|
+
let p = i;
|
|
180
|
+
let closes = false;
|
|
181
|
+
while (p < text.length && !closes) {
|
|
182
|
+
if (text[p] === "`") {
|
|
183
|
+
const r = Markdown.runLength(text, p);
|
|
184
|
+
if (r === n)
|
|
185
|
+
closes = true;
|
|
186
|
+
p += r;
|
|
187
|
+
}
|
|
188
|
+
else if (text[p] === "\n" && /^[ \t]*\r?(?:\n|$)/.test(text.slice(p + 1)))
|
|
189
|
+
/* a code span never crosses a paragraph break */
|
|
190
|
+
break;
|
|
191
|
+
else
|
|
192
|
+
p++;
|
|
193
|
+
}
|
|
194
|
+
if (closes)
|
|
195
|
+
fence = n;
|
|
172
196
|
}
|
|
173
|
-
if (fence === 0)
|
|
174
|
-
/* open a span of fence width n */
|
|
175
|
-
fence = n;
|
|
176
197
|
else if (n === fence)
|
|
177
198
|
/* close the active span */
|
|
178
199
|
fence = 0;
|
|
@@ -203,23 +224,35 @@ export class Markdown {
|
|
|
203
224
|
i++;
|
|
204
225
|
}
|
|
205
226
|
/* close span before the break, re-open after indentation,
|
|
206
|
-
preserving the active fence width on both sides
|
|
227
|
+
preserving the active fence width on both sides and padding
|
|
228
|
+
with a space wherever an inserted delimiter run would merge
|
|
229
|
+
with an adjacent literal backtick of the span content */
|
|
207
230
|
const bars = "`".repeat(fence);
|
|
208
|
-
out
|
|
231
|
+
const padC = out.endsWith("`") ? " " : "";
|
|
232
|
+
const padO = (i < text.length && text[i] === "`") ? " " : "";
|
|
233
|
+
out += `${padC}${bars}${nl}${ws}${bars}${padO}`;
|
|
209
234
|
continue;
|
|
210
235
|
}
|
|
211
236
|
out += ch;
|
|
212
237
|
i++;
|
|
213
238
|
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
/* apply the inline-span and bullet-marker rewriting passes to a chunk of
|
|
242
|
+
non-fenced Markdown text (see prepare() for fenced-block handling) */
|
|
243
|
+
static rewrite(text) {
|
|
244
|
+
/* PASS 1: rewrite escaped-backtick inline code spans */
|
|
245
|
+
text = Markdown.rewriteEscapedSpans(text);
|
|
246
|
+
/* PASS 2: split multi-line inline code spans into per-line spans */
|
|
247
|
+
const out = Markdown.splitMultiLineSpans(text);
|
|
214
248
|
/* PASS 3: replace the leading "-"/"*" marker of every unordered
|
|
215
249
|
bullet paragraph with "◯", preserving the leading indentation
|
|
216
250
|
and the following whitespace; operate line-by-line so only the
|
|
217
251
|
actual list markers (at a line start) are affected */
|
|
218
|
-
|
|
252
|
+
return out
|
|
219
253
|
.split("\n")
|
|
220
254
|
.map((line) => line.replace(/^(\s*)[-*]([ \t]+)/, "$1◯$2"))
|
|
221
255
|
.join("\n");
|
|
222
|
-
return out;
|
|
223
256
|
}
|
|
224
257
|
}
|
|
225
258
|
/* MCP registration entry point */
|
package/dst/ase-mcp.js
CHANGED
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
5
|
*/
|
|
6
|
-
import path from "node:path";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
8
7
|
import { execa } from "execa";
|
|
9
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
9
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
11
|
-
import {
|
|
12
|
-
import { SERVICE_HOST as HOST, serviceSchema, probe } from "./ase-service.js";
|
|
10
|
+
import { SERVICE_HOST as HOST, probe, loadServiceContext } from "./ase-service.js";
|
|
13
11
|
/* CLI command "ase mcp" */
|
|
14
12
|
export default class MCPCommand {
|
|
15
13
|
log;
|
|
@@ -18,13 +16,7 @@ export default class MCPCommand {
|
|
|
18
16
|
}
|
|
19
17
|
/* load service identity context */
|
|
20
18
|
loadContext() {
|
|
21
|
-
|
|
22
|
-
cfg.read();
|
|
23
|
-
const svc = new Config("service", serviceSchema, this.log);
|
|
24
|
-
svc.read();
|
|
25
|
-
const projectId = cfg.get("project.id") ?? path.basename(process.cwd());
|
|
26
|
-
const port = svc.get("port") ?? null;
|
|
27
|
-
return { projectId, port };
|
|
19
|
+
return loadServiceContext(this.log);
|
|
28
20
|
}
|
|
29
21
|
/* run "ase service start" and wait for the service to come up */
|
|
30
22
|
async ensureService() {
|
|
@@ -116,6 +108,7 @@ export default class MCPCommand {
|
|
|
116
108
|
try {
|
|
117
109
|
const ctx = await this.ensureService();
|
|
118
110
|
port = ctx.port;
|
|
111
|
+
projectId = ctx.projectId;
|
|
119
112
|
const stale = client;
|
|
120
113
|
client = null;
|
|
121
114
|
if (stale !== null) {
|
package/dst/ase-meta.js
CHANGED
|
@@ -39,8 +39,9 @@ export class Meta {
|
|
|
39
39
|
try {
|
|
40
40
|
return fs.readFileSync(abs, "utf8");
|
|
41
41
|
}
|
|
42
|
-
catch (
|
|
43
|
-
|
|
42
|
+
catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
throw new Error(`meta: failed to read file: ${abs} (${message})`);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -58,8 +59,9 @@ export default class MetaCommand {
|
|
|
58
59
|
.argument("<name...>", "meta file name(s) (\"ase-\" prefix and \".md\" extension optional)")
|
|
59
60
|
.action(async (names) => {
|
|
60
61
|
this.log.write("debug", `meta: reading ${names.length} file(s)`);
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const texts = names.map((name) => Meta.read(name));
|
|
63
|
+
for (const text of texts)
|
|
64
|
+
await writeStdout(text);
|
|
63
65
|
});
|
|
64
66
|
}
|
|
65
67
|
}
|
package/dst/ase-persona.js
CHANGED
|
@@ -5,17 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isScalar } from "yaml";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { Config, configSchema, parseScope } from "./ase-config.js";
|
|
8
|
+
import { Config, configSchema, parseScope, agentClassification } from "./ase-config.js";
|
|
9
9
|
/* reusable functionality: ASE agent persona style get/set */
|
|
10
10
|
export class Persona {
|
|
11
11
|
/* allowed persona style values */
|
|
12
|
-
static styles =
|
|
12
|
+
static styles = agentClassification.persona;
|
|
13
13
|
/* get the effective persona style for an optional session;
|
|
14
14
|
returns the default "engineer" if nothing is configured */
|
|
15
15
|
static get(log, session) {
|
|
16
|
-
const scope = session !== undefined ?
|
|
17
|
-
parseScope(`session:${session}`) :
|
|
18
|
-
parseScope(undefined);
|
|
16
|
+
const scope = parseScope(session !== undefined ? `session:${session}` : undefined);
|
|
19
17
|
const cfg = new Config("config", configSchema, log, scope);
|
|
20
18
|
cfg.read();
|
|
21
19
|
const val = cfg.get("agent.persona");
|
|
@@ -28,9 +26,7 @@ export class Persona {
|
|
|
28
26
|
}
|
|
29
27
|
/* set the persona style on the strongest scope of an optional session */
|
|
30
28
|
static set(log, style, session) {
|
|
31
|
-
const scope = session !== undefined ?
|
|
32
|
-
parseScope(`session:${session}`) :
|
|
33
|
-
parseScope(undefined);
|
|
29
|
+
const scope = parseScope(session !== undefined ? `session:${session}` : undefined);
|
|
34
30
|
const cfg = new Config("config", configSchema, log, scope);
|
|
35
31
|
cfg.lock(() => {
|
|
36
32
|
cfg.read();
|
|
@@ -61,7 +57,7 @@ export default class PersonaMCP {
|
|
|
61
57
|
inputSchema: {
|
|
62
58
|
style: z.enum(Persona.styles).optional()
|
|
63
59
|
.describe("persona style to set; if omitted, the current persona style is returned"),
|
|
64
|
-
session: z.string().optional()
|
|
60
|
+
session: z.string().regex(/^[A-Za-z0-9._-]+$/).optional()
|
|
65
61
|
.describe("session identifier (allowed characters: A-Z, a-z, 0-9, '.', '_', '-'); " +
|
|
66
62
|
"if omitted, the operation is not scoped to a specific session")
|
|
67
63
|
}
|
|
@@ -71,7 +67,7 @@ export default class PersonaMCP {
|
|
|
71
67
|
Persona.set(this.log, args.style, args.session);
|
|
72
68
|
const where = args.session !== undefined ?
|
|
73
69
|
` for session "${args.session}"` : "";
|
|
74
|
-
const msg = `
|
|
70
|
+
const msg = `OK: set agent.persona to "${args.style}"${where}`;
|
|
75
71
|
return {
|
|
76
72
|
content: [{ type: "text", text: msg }]
|
|
77
73
|
};
|
|
@@ -85,7 +81,7 @@ export default class PersonaMCP {
|
|
|
85
81
|
const message = err instanceof Error ? err.message : String(err);
|
|
86
82
|
return {
|
|
87
83
|
isError: true,
|
|
88
|
-
content: [{ type: "text", text: `
|
|
84
|
+
content: [{ type: "text", text: `ERROR: ${message}` }]
|
|
89
85
|
};
|
|
90
86
|
}
|
|
91
87
|
});
|