@rse/ase 0.0.61 → 0.9.0
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-diagram.js +34 -41
- package/dst/ase-mcp.js +19 -21
- package/dst/ase-persona.js +2 -2
- package/dst/ase-statusline.js +2 -2
- package/package.json +5 -5
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/agents/ase-code-lint.md +6 -0
- package/plugin/package.json +1 -1
- package/plugin/skills/ase-docs-proofread/help.md +1 -1
- package/plugin/skills/{ase-meta-changes → ase-meta-changelog}/SKILL.md +2 -2
- package/plugin/skills/{ase-meta-changes → ase-meta-changelog}/help.md +4 -4
- package/plugin/skills/ase-meta-commit/help.md +1 -1
- package/plugin/skills/ase-meta-diff/help.md +1 -1
- package/dst/ase-hello.js +0 -27
package/dst/ase-diagram.js
CHANGED
|
@@ -20,6 +20,15 @@ const parseColorMode = (name) => (value) => {
|
|
|
20
20
|
throw new InvalidArgumentError(`${name} must be "none", "ansi16", or "ansi256"`);
|
|
21
21
|
return value;
|
|
22
22
|
};
|
|
23
|
+
/* scan a CSI escape sequence starting at line[i] (where line[i]===ESC and
|
|
24
|
+
line[i+1]==="["); return the index just past the terminating letter, or
|
|
25
|
+
-1 if the sequence is unterminated within the line */
|
|
26
|
+
const scanAnsiSeq = (line, i) => {
|
|
27
|
+
let j = i + 2;
|
|
28
|
+
while (j < line.length && !/[A-Za-z]/.test(line[j]))
|
|
29
|
+
j++;
|
|
30
|
+
return j < line.length ? j + 1 : -1;
|
|
31
|
+
};
|
|
23
32
|
/* truncate a single rendered line to a maximum visible column,
|
|
24
33
|
preserving ANSI escape sequences (CSI ...m) and appending an ANSI
|
|
25
34
|
reset sequence if any styling was active at the truncation point */
|
|
@@ -33,20 +42,15 @@ const truncateAnsiLine = (line, budget) => {
|
|
|
33
42
|
while (i < line.length) {
|
|
34
43
|
const ch = line[i];
|
|
35
44
|
if (ch === "\x1b" && line[i + 1] === "[") {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
j
|
|
39
|
-
if (j < line.length) {
|
|
40
|
-
const seq = line.slice(i, j + 1);
|
|
45
|
+
const j = scanAnsiSeq(line, i);
|
|
46
|
+
if (j >= 0) {
|
|
47
|
+
const seq = line.slice(i, j);
|
|
41
48
|
out += seq;
|
|
42
49
|
if (seq.endsWith("m")) {
|
|
43
50
|
const body = seq.slice(2, -1);
|
|
44
|
-
|
|
45
|
-
styled = false;
|
|
46
|
-
else
|
|
47
|
-
styled = true;
|
|
51
|
+
styled = !(body === "" || body === "0");
|
|
48
52
|
}
|
|
49
|
-
i = j
|
|
53
|
+
i = j;
|
|
50
54
|
continue;
|
|
51
55
|
}
|
|
52
56
|
i++;
|
|
@@ -70,11 +74,9 @@ const visibleWidth = (line) => {
|
|
|
70
74
|
while (i < line.length) {
|
|
71
75
|
const ch = line[i];
|
|
72
76
|
if (ch === "\x1b" && line[i + 1] === "[") {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
j
|
|
76
|
-
if (j < line.length) {
|
|
77
|
-
i = j + 1;
|
|
77
|
+
const j = scanAnsiSeq(line, i);
|
|
78
|
+
if (j >= 0) {
|
|
79
|
+
i = j;
|
|
78
80
|
continue;
|
|
79
81
|
}
|
|
80
82
|
i++;
|
|
@@ -87,39 +89,30 @@ const visibleWidth = (line) => {
|
|
|
87
89
|
};
|
|
88
90
|
/* reusable functionality: Mermaid diagram rendering as Unicode/ASCII art */
|
|
89
91
|
export class Diagram {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let width = 0;
|
|
92
|
+
static detectTermDimension(envVar, stdoutProp) {
|
|
93
|
+
let value = 0;
|
|
93
94
|
/* attempt 1: query environment variable */
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
const env = process.env[envVar];
|
|
96
|
+
if (env !== undefined) {
|
|
97
|
+
const n = Number.parseInt(env, 10);
|
|
98
|
+
if (Number.isFinite(n) && n > 0)
|
|
99
|
+
value = n;
|
|
98
100
|
}
|
|
99
101
|
/* attempt 2: query stdout */
|
|
100
|
-
if (
|
|
101
|
-
const
|
|
102
|
-
if (typeof
|
|
103
|
-
|
|
102
|
+
if (value === 0 && process.stdout.isTTY) {
|
|
103
|
+
const n = process.stdout[stdoutProp];
|
|
104
|
+
if (typeof n === "number" && n > 0)
|
|
105
|
+
value = n;
|
|
104
106
|
}
|
|
105
|
-
return
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
/* detect terminal column width */
|
|
110
|
+
static detectTermWidth() {
|
|
111
|
+
return Diagram.detectTermDimension("ASE_TERM_WIDTH", "columns");
|
|
106
112
|
}
|
|
107
113
|
/* detect terminal row height */
|
|
108
114
|
static detectTermHeight() {
|
|
109
|
-
|
|
110
|
-
/* attempt 1: query environment variable */
|
|
111
|
-
if (process.env.ASE_TERM_HEIGHT !== undefined) {
|
|
112
|
-
const rows = Number.parseInt(process.env.ASE_TERM_HEIGHT, 10);
|
|
113
|
-
if (Number.isFinite(rows) && rows > 0)
|
|
114
|
-
height = rows;
|
|
115
|
-
}
|
|
116
|
-
/* attempt 2: query stdout */
|
|
117
|
-
if (height === 0 && process.stdout.isTTY) {
|
|
118
|
-
const rows = process.stdout.rows;
|
|
119
|
-
if (typeof rows === "number" && rows > 0)
|
|
120
|
-
height = rows;
|
|
121
|
-
}
|
|
122
|
-
return height;
|
|
115
|
+
return Diagram.detectTermDimension("ASE_TERM_HEIGHT", "rows");
|
|
123
116
|
}
|
|
124
117
|
/* detect terminal color capability */
|
|
125
118
|
static detectColorMode() {
|
package/dst/ase-mcp.js
CHANGED
|
@@ -22,10 +22,8 @@ export default class MCPCommand {
|
|
|
22
22
|
cfg.read();
|
|
23
23
|
const svc = new Config("service", serviceSchema, this.log);
|
|
24
24
|
svc.read();
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const rawPort = svc.get("port");
|
|
28
|
-
const port = rawPort ?? null;
|
|
25
|
+
const projectId = cfg.get("project.id") ?? path.basename(process.cwd());
|
|
26
|
+
const port = svc.get("port") ?? null;
|
|
29
27
|
return { projectId, port };
|
|
30
28
|
}
|
|
31
29
|
/* run "ase service start" and wait for the service to come up */
|
|
@@ -59,7 +57,7 @@ export default class MCPCommand {
|
|
|
59
57
|
/* bridge stdio to a Streamable HTTP MCP endpoint on the local service */
|
|
60
58
|
async runBridge() {
|
|
61
59
|
/* ensure the service is running */
|
|
62
|
-
let { port } = await this.ensureService();
|
|
60
|
+
let { projectId, port } = await this.ensureService();
|
|
63
61
|
/* create MCP STDIO server (lives for the entire bridge lifetime) */
|
|
64
62
|
const server = new StdioServerTransport();
|
|
65
63
|
/* track active client and bridge-level closed state */
|
|
@@ -96,19 +94,17 @@ export default class MCPCommand {
|
|
|
96
94
|
next.onclose = () => {
|
|
97
95
|
if (client !== next || closedByUs || bridgeDone || reconnecting)
|
|
98
96
|
return;
|
|
99
|
-
|
|
100
|
-
this.log.write("warning", "mcp: http connection lost — reconnecting");
|
|
101
|
-
reconnect(0, () => { reconnecting = false; }).catch(() => { });
|
|
97
|
+
triggerReconnect("http connection lost");
|
|
102
98
|
};
|
|
103
99
|
await next.start();
|
|
104
100
|
client = next;
|
|
105
101
|
};
|
|
106
102
|
/* reconnect loop: restart service if needed, then reconnect client */
|
|
107
|
-
const reconnect = async (attempt = 0
|
|
103
|
+
const reconnect = async (attempt = 0) => {
|
|
108
104
|
const delay = Math.min(500 * 2 ** attempt, 10000);
|
|
109
105
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
110
106
|
if (bridgeDone) {
|
|
111
|
-
|
|
107
|
+
reconnecting = false;
|
|
112
108
|
return;
|
|
113
109
|
}
|
|
114
110
|
try {
|
|
@@ -118,15 +114,21 @@ export default class MCPCommand {
|
|
|
118
114
|
await client?.close();
|
|
119
115
|
await connectClient();
|
|
120
116
|
closedByUs = false;
|
|
117
|
+
reconnecting = false;
|
|
121
118
|
this.log.write("info", "mcp: reconnected to service");
|
|
122
|
-
done?.();
|
|
123
119
|
}
|
|
124
120
|
catch (err) {
|
|
125
121
|
closedByUs = false;
|
|
126
122
|
this.log.write("error", `mcp: reconnect failed: ${this.asError(err).message}`);
|
|
127
|
-
reconnect(attempt + 1
|
|
123
|
+
reconnect(attempt + 1).catch(() => { });
|
|
128
124
|
}
|
|
129
125
|
};
|
|
126
|
+
/* trigger a reconnect chain (idempotent while one is active) */
|
|
127
|
+
const triggerReconnect = (reason) => {
|
|
128
|
+
reconnecting = true;
|
|
129
|
+
this.log.write("warning", `mcp: ${reason} — reconnecting`);
|
|
130
|
+
reconnect(0).catch(() => { });
|
|
131
|
+
};
|
|
130
132
|
/* wire stdio server */
|
|
131
133
|
server.onmessage = (msg) => {
|
|
132
134
|
client?.send(msg).catch((err) => {
|
|
@@ -148,16 +150,12 @@ export default class MCPCommand {
|
|
|
148
150
|
if (bridgeDone || reconnecting)
|
|
149
151
|
return;
|
|
150
152
|
try {
|
|
151
|
-
const { projectId } = this.loadContext();
|
|
152
153
|
const match = await probe(port, projectId);
|
|
153
|
-
if (match !== true)
|
|
154
|
-
|
|
155
|
-
this.log.write("warning", "mcp: health check failed — reconnecting");
|
|
156
|
-
reconnect(0, () => { reconnecting = false; }).catch(() => { });
|
|
157
|
-
}
|
|
154
|
+
if (match !== true)
|
|
155
|
+
triggerReconnect("health check failed");
|
|
158
156
|
}
|
|
159
157
|
catch (err) {
|
|
160
|
-
/* ignore transient probe
|
|
158
|
+
/* ignore transient probe errors but record them */
|
|
161
159
|
this.log.write("debug", `mcp: health check error: ${this.asError(err).message}`);
|
|
162
160
|
}
|
|
163
161
|
}, HEALTH_INTERVAL_MS);
|
|
@@ -171,7 +169,6 @@ export default class MCPCommand {
|
|
|
171
169
|
/* shutdown services */
|
|
172
170
|
clearInterval(healthTimer);
|
|
173
171
|
await shutdown();
|
|
174
|
-
return 0; /* unreachable, kept only to satisfy the Promise<number> return type */
|
|
175
172
|
}
|
|
176
173
|
/* register commands */
|
|
177
174
|
register(program) {
|
|
@@ -179,7 +176,8 @@ export default class MCPCommand {
|
|
|
179
176
|
.command("mcp")
|
|
180
177
|
.description("Bridge stdio MCP to the per-project background service over Streamable HTTP")
|
|
181
178
|
.action(async () => {
|
|
182
|
-
|
|
179
|
+
await this.runBridge();
|
|
180
|
+
process.exit(0);
|
|
183
181
|
});
|
|
184
182
|
}
|
|
185
183
|
}
|
package/dst/ase-persona.js
CHANGED
|
@@ -51,14 +51,14 @@ export default class PersonaMCP {
|
|
|
51
51
|
"If `session` is provided, the operation is scoped to that session, " +
|
|
52
52
|
"otherwise it operates on the strongest/closest scope (user/project cascade). " +
|
|
53
53
|
"Allowed styles: \"writer\" (decorative, eloquent, explaining), " +
|
|
54
|
-
"\"engineer\" (
|
|
54
|
+
"\"engineer\" (concise, factual, accurate), " +
|
|
55
55
|
"\"telegrapher\" (very brief, factual, abbreviating), " +
|
|
56
56
|
"\"caveman\" (ultra brief, rough, stuttering).",
|
|
57
57
|
inputSchema: {
|
|
58
58
|
style: z.enum(Persona.styles).optional()
|
|
59
59
|
.describe("persona style to set; if omitted, the current persona style is returned"),
|
|
60
60
|
session: z.string().optional()
|
|
61
|
-
.describe("session identifier (allowed characters: A-Z, a-z, 0-9, '-'); " +
|
|
61
|
+
.describe("session identifier (allowed characters: A-Z, a-z, 0-9, '.', '_', '-'); " +
|
|
62
62
|
"if omitted, the operation is not scoped to a specific session")
|
|
63
63
|
}
|
|
64
64
|
}, async (args) => {
|
package/dst/ase-statusline.js
CHANGED
|
@@ -346,10 +346,10 @@ export default class StatuslineCommand {
|
|
|
346
346
|
emit(`${prefix("◔", "context")}${bar} ${pct}%`);
|
|
347
347
|
},
|
|
348
348
|
C: () => {
|
|
349
|
-
const
|
|
349
|
+
const pct = Math.floor(data.context_window?.used_percentage ?? 0);
|
|
350
350
|
const tokensCur = (data.context_window?.total_input_tokens ?? 0) +
|
|
351
351
|
(data.context_window?.total_output_tokens ?? 0);
|
|
352
|
-
const tokensLim =
|
|
352
|
+
const tokensLim = pct > 0 && tokensCur > 0 ? Math.round(tokensCur * 100 / pct) : 0;
|
|
353
353
|
if (tokensLim > 0)
|
|
354
354
|
emit(`${prefix("◆", "tokens")}${c.bold(formatTokens(tokensCur) + "/" + formatTokens(tokensLim))}`);
|
|
355
355
|
},
|
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
|
|
9
|
+
"version": "0.9.0",
|
|
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.
|
|
22
|
-
"@typescript-eslint/eslint-plugin": "8.60.
|
|
21
|
+
"@typescript-eslint/parser": "8.60.1",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "8.60.1",
|
|
23
23
|
"eslint-plugin-promise": "7.3.0",
|
|
24
24
|
"eslint-plugin-import": "2.32.0",
|
|
25
25
|
"neostandard": "0.13.0",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@types/pacote": "11.1.8"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"commander": "
|
|
44
|
-
"@dotenvx/dotenvx": "1.
|
|
43
|
+
"commander": "15.0.0",
|
|
44
|
+
"@dotenvx/dotenvx": "1.71.0",
|
|
45
45
|
"yaml": "2.9.0",
|
|
46
46
|
"valibot": "1.4.1",
|
|
47
47
|
"execa": "9.6.1",
|
|
@@ -269,6 +269,7 @@ Workflow
|
|
|
269
269
|
|
|
270
270
|
<template>
|
|
271
271
|
● **WHAT**: [...]
|
|
272
|
+
|
|
272
273
|
○ **WHY**: [...]
|
|
273
274
|
</template>
|
|
274
275
|
|
|
@@ -279,6 +280,11 @@ Workflow
|
|
|
279
280
|
text with Markdown based on the following <template/>:
|
|
280
281
|
<template>"`<words/>`"</template>.
|
|
281
282
|
|
|
283
|
+
For all code references, always use a relative filename and
|
|
284
|
+
append the related single 1-based line number N as `:N` or the
|
|
285
|
+
related 1-based line number range as `:N-M` to the end of the
|
|
286
|
+
filename.
|
|
287
|
+
|
|
282
288
|
4. Create the change set.
|
|
283
289
|
For this, set <change-set></change-set> (set changes to empty).
|
|
284
290
|
|
package/plugin/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
|
|
9
|
+
"version": "0.9.0",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: ase-meta-
|
|
2
|
+
name: ase-meta-changelog
|
|
3
3
|
argument-hint: "[--help|-h]"
|
|
4
4
|
description: >
|
|
5
5
|
Update changes entries in CHANGELOG.md files
|
|
@@ -17,7 +17,7 @@ allowed-tools:
|
|
|
17
17
|
@${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
|
|
18
18
|
@${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
|
|
19
19
|
|
|
20
|
-
<skill name="ase-meta-
|
|
20
|
+
<skill name="ase-meta-changelog">
|
|
21
21
|
Update ChangeLog Entries
|
|
22
22
|
</skill>
|
|
23
23
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
## NAME
|
|
3
3
|
|
|
4
|
-
`ase-meta-
|
|
4
|
+
`ase-meta-changelog` - Update ChangeLog Entries
|
|
5
5
|
|
|
6
6
|
## SYNOPSIS
|
|
7
7
|
|
|
8
|
-
`ase-meta-
|
|
8
|
+
`ase-meta-changelog`
|
|
9
9
|
[`--help`|`-h`]
|
|
10
10
|
|
|
11
11
|
## DESCRIPTION
|
|
12
12
|
|
|
13
|
-
The `ase-meta-
|
|
13
|
+
The `ase-meta-changelog` skill helps to *complete*, *consolidate*, and
|
|
14
14
|
*sort* the entries of the most recent section of a `CHANGELOG.md`
|
|
15
15
|
file, based on the underlying *Git* commits and the currently staged
|
|
16
16
|
changes in the Git *index*.
|
|
@@ -25,7 +25,7 @@ date in the section header is also updated to the current date.
|
|
|
25
25
|
Update the most recent ChangeLog section:
|
|
26
26
|
|
|
27
27
|
```text
|
|
28
|
-
❯ /ase-meta-
|
|
28
|
+
❯ /ase-meta-changelog
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## SEE ALSO
|
package/dst/ase-hello.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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 { Chalk } from "chalk";
|
|
7
|
-
/* forced-color chalk instance: stdout is a pipe under Claude Code,
|
|
8
|
-
so chalk auto-detection would yield level 0; force level 1 to keep
|
|
9
|
-
emitting ANSI sequences as the original implementation did */
|
|
10
|
-
const c = new Chalk({ level: 1 });
|
|
11
|
-
/* command-line handling */
|
|
12
|
-
export default class HelloCommand {
|
|
13
|
-
log;
|
|
14
|
-
constructor(log) {
|
|
15
|
-
this.log = log;
|
|
16
|
-
}
|
|
17
|
-
/* register commands */
|
|
18
|
-
register(program) {
|
|
19
|
-
program
|
|
20
|
-
.command("hello")
|
|
21
|
-
.description("Show a greeting message")
|
|
22
|
-
.option("-s, --subject <subject>", "subject to greet", "Universe")
|
|
23
|
-
.action(async (opts) => {
|
|
24
|
-
process.stdout.write(c.red(`${opts.subject} World!`) + "\n");
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}
|