@rse/ase 0.9.58 → 0.9.60
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-hook.js +45 -5
- package/dst/ase-markdown.js +1 -1
- package/dst/ase-meta.js +1 -1
- package/dst/ase-service.js +2 -0
- package/dst/ase-setup.js +1 -1
- package/dst/ase-sleep.js +3 -3
- package/dst/ase-statusline.js +1 -1
- package/dst/ase-worktree.js +162 -0
- package/dst/ase.js +2 -0
- package/package.json +12 -13
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/agents/ase-code-lint.md +15 -0
- package/plugin/agents/ase-docs-proofread.md +15 -0
- package/plugin/meta/ase-common-code.md +1 -1
- package/plugin/meta/ase-common-task.md +8 -8
- package/plugin/meta/ase-control.md +26 -8
- package/plugin/meta/ase-dialog.md +4 -2
- package/plugin/package.json +4 -4
- package/plugin/skills/ase-code-craft/SKILL.md +43 -9
- package/plugin/skills/ase-code-craft/help.md +13 -0
- package/plugin/skills/ase-code-dissect/SKILL.md +30 -9
- package/plugin/skills/ase-code-edit/SKILL.md +472 -0
- package/plugin/skills/ase-code-edit/help.md +127 -0
- package/plugin/skills/ase-code-lint/SKILL.md +26 -0
- package/plugin/skills/ase-code-refactor/SKILL.md +7 -6
- package/plugin/skills/ase-code-resolve/SKILL.md +7 -6
- package/plugin/skills/ase-docs-proofread/SKILL.md +21 -0
- package/plugin/skills/ase-help-skill/catalog.md +1 -0
- package/plugin/skills/ase-sync-import/SKILL.md +2 -1
- package/plugin/skills/ase-sync-reconcile/SKILL.md +3 -1
- package/plugin/skills/ase-task-condense/SKILL.md +13 -13
- package/plugin/skills/ase-task-dissect/SKILL.md +6 -5
- package/plugin/skills/ase-task-edit/SKILL.md +42 -20
- package/plugin/skills/ase-task-edit/help.md +7 -0
- package/plugin/skills/ase-task-grill/SKILL.md +4 -4
- package/plugin/skills/ase-task-implement/SKILL.md +39 -24
- package/plugin/skills/ase-task-implement/help.md +4 -2
- package/plugin/skills/ase-task-preflight/SKILL.md +18 -4
- package/plugin/skills/ase-task-preflight/help.md +5 -1
- package/plugin/skills/ase-task-reboot/SKILL.md +1 -1
- package/plugin/skills/ase-task-view/SKILL.md +1 -2
package/dst/ase-hook.js
CHANGED
|
@@ -75,6 +75,10 @@ const toolInputSchema = v.object({
|
|
|
75
75
|
skill: v.optional(v.string()),
|
|
76
76
|
file_path: v.optional(v.string())
|
|
77
77
|
});
|
|
78
|
+
/* maximum tolerated age of an idle session directory: the session-end hook
|
|
79
|
+
removes it regularly, but a crashed or SIGKILLed agent leaves it behind
|
|
80
|
+
forever, so orphans are garbage-collected once they exceed this age */
|
|
81
|
+
const SESSION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
78
82
|
/* CLI command "ase hook" */
|
|
79
83
|
export default class HookCommand {
|
|
80
84
|
log;
|
|
@@ -85,6 +89,40 @@ export default class HookCommand {
|
|
|
85
89
|
isValidSessionId(id) {
|
|
86
90
|
return /^[A-Za-z0-9._-]+$/.test(id);
|
|
87
91
|
}
|
|
92
|
+
/* resolve the base directory holding all per-session state */
|
|
93
|
+
sessionBaseDir() {
|
|
94
|
+
return path.join(os.homedir(), ".ase", "session");
|
|
95
|
+
}
|
|
96
|
+
/* garbage-collect orphaned session directories left behind by agents
|
|
97
|
+
which died before their session-end hook could run; a live session
|
|
98
|
+
keeps its directory's mtime current, as every tool call acquires a
|
|
99
|
+
lock file inside it, so plain age is a reliable liveness signal */
|
|
100
|
+
pruneStaleSessions(currentSessionId) {
|
|
101
|
+
const base = this.sessionBaseDir();
|
|
102
|
+
let entries;
|
|
103
|
+
try {
|
|
104
|
+
entries = fs.readdirSync(base, { withFileTypes: true });
|
|
105
|
+
}
|
|
106
|
+
catch (_e) {
|
|
107
|
+
/* best-effort: no base directory yet, or unreadable */
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const deadline = Date.now() - SESSION_MAX_AGE_MS;
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (!entry.isDirectory() || entry.name === currentSessionId)
|
|
113
|
+
continue;
|
|
114
|
+
const dir = path.join(base, entry.name);
|
|
115
|
+
try {
|
|
116
|
+
if (fs.statSync(dir).mtimeMs >= deadline)
|
|
117
|
+
continue;
|
|
118
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
119
|
+
this.log.write("debug", `hook: pruned stale session directory: ${dir}`);
|
|
120
|
+
}
|
|
121
|
+
catch (_e) {
|
|
122
|
+
/* best-effort: ignore vanished or undeletable directories */
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
88
126
|
/* drain and discard the stdin event payload */
|
|
89
127
|
async drainStdin() {
|
|
90
128
|
await readStdin().catch(() => "");
|
|
@@ -176,14 +214,14 @@ export default class HookCommand {
|
|
|
176
214
|
try {
|
|
177
215
|
pkg = fs.readFileSync(filePkg, "utf8");
|
|
178
216
|
}
|
|
179
|
-
catch (
|
|
180
|
-
throw new Error(`failed to read plugin manifest: ${filePkg}
|
|
217
|
+
catch (err) {
|
|
218
|
+
throw new Error(`failed to read plugin manifest: ${filePkg}`, { cause: err });
|
|
181
219
|
}
|
|
182
220
|
try {
|
|
183
221
|
md = fs.readFileSync(fileMd, "utf8");
|
|
184
222
|
}
|
|
185
|
-
catch (
|
|
186
|
-
throw new Error(`failed to read constitution file: ${fileMd}
|
|
223
|
+
catch (err) {
|
|
224
|
+
throw new Error(`failed to read constitution file: ${fileMd}`, { cause: err });
|
|
187
225
|
}
|
|
188
226
|
/* determine own version */
|
|
189
227
|
const pkgObj = this.parseJSON(pkg, v.object({ version: v.optional(v.string()) }));
|
|
@@ -210,6 +248,8 @@ export default class HookCommand {
|
|
|
210
248
|
}));
|
|
211
249
|
/* determine session id */
|
|
212
250
|
const sessionId = this.pickSessionId(input);
|
|
251
|
+
/* garbage-collect orphaned session directories of previous agent runs */
|
|
252
|
+
this.pruneStaleSessions(sessionId);
|
|
213
253
|
/* establish config context (session-scoped only if a valid sessionId is present) */
|
|
214
254
|
const hasSession = this.isValidSessionId(sessionId);
|
|
215
255
|
const cfg = new Config("config", configSchema, this.log, hasSession ? parseScope(`session:${sessionId}`) : parseScope(undefined));
|
|
@@ -363,7 +403,7 @@ export default class HookCommand {
|
|
|
363
403
|
const sessionId = await this.readSessionIdFromStdin();
|
|
364
404
|
/* remove the session directory ~/.ase/session/<id> (only for a valid sessionId) */
|
|
365
405
|
if (this.isValidSessionId(sessionId)) {
|
|
366
|
-
const dir = path.join(
|
|
406
|
+
const dir = path.join(this.sessionBaseDir(), sessionId);
|
|
367
407
|
try {
|
|
368
408
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
369
409
|
}
|
package/dst/ase-markdown.js
CHANGED
|
@@ -215,7 +215,7 @@ export class Markdown {
|
|
|
215
215
|
}
|
|
216
216
|
if (fence > 0 && (ch === "\r" || ch === "\n")) {
|
|
217
217
|
/* consume optional CR followed by mandatory LF */
|
|
218
|
-
let nl
|
|
218
|
+
let nl;
|
|
219
219
|
if (ch === "\r" && i + 1 < text.length && text[i + 1] === "\n") {
|
|
220
220
|
nl = "\r\n";
|
|
221
221
|
i += 2;
|
package/dst/ase-meta.js
CHANGED
|
@@ -41,7 +41,7 @@ export class Meta {
|
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
43
|
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
-
throw new Error(`meta: failed to read file: ${abs} (${message})
|
|
44
|
+
throw new Error(`meta: failed to read file: ${abs} (${message})`, { cause: err });
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
package/dst/ase-service.js
CHANGED
|
@@ -27,6 +27,7 @@ import { TimestampMCP } from "./ase-timestamp.js";
|
|
|
27
27
|
import { SleepMCP } from "./ase-sleep.js";
|
|
28
28
|
import { GetoptMCP } from "./ase-getopt.js";
|
|
29
29
|
import { SkillsMCP } from "./ase-skills.js";
|
|
30
|
+
import { WorktreeMCP } from "./ase-worktree.js";
|
|
30
31
|
import pkg from "../package.json" with { type: "json" };
|
|
31
32
|
/* shared service host */
|
|
32
33
|
export const SERVICE_HOST = "127.0.0.1";
|
|
@@ -272,6 +273,7 @@ export default class ServiceCommand {
|
|
|
272
273
|
new SleepMCP().register(mcp);
|
|
273
274
|
new GetoptMCP().register(mcp);
|
|
274
275
|
new SkillsMCP().register(mcp);
|
|
276
|
+
new WorktreeMCP().register(mcp);
|
|
275
277
|
new ConfigMCP(this.log).register(mcp);
|
|
276
278
|
return mcp;
|
|
277
279
|
};
|
package/dst/ase-setup.js
CHANGED
|
@@ -47,7 +47,7 @@ export default class SetupCommand {
|
|
|
47
47
|
return false;
|
|
48
48
|
/* determine the npm global prefix and probe writability of the
|
|
49
49
|
directories that "npm -g" actually mutates */
|
|
50
|
-
let prefix
|
|
50
|
+
let prefix;
|
|
51
51
|
try {
|
|
52
52
|
const result = await execa("npm", ["prefix", "-g"], { stdio: "pipe" });
|
|
53
53
|
prefix = result.stdout.trim();
|
package/dst/ase-sleep.js
CHANGED
|
@@ -10,11 +10,11 @@ export class SleepMCP {
|
|
|
10
10
|
mcp.registerTool("ase_sleep", {
|
|
11
11
|
title: "ASE sleep",
|
|
12
12
|
description: "Wait once for `duration` seconds and then return. " +
|
|
13
|
-
"The duration can be fractional (e.g. `1.5`). " +
|
|
13
|
+
"The duration can be fractional (e.g. `1.5`) and is at most 120 (default: 60). " +
|
|
14
14
|
"Returns `OK: slept <duration> seconds` as `text` after the duration elapsed.",
|
|
15
15
|
inputSchema: {
|
|
16
|
-
duration: z.number().positive().max(
|
|
17
|
-
.describe("wait duration in seconds (fractional values allowed, at most
|
|
16
|
+
duration: z.number().positive().max(120).default(60)
|
|
17
|
+
.describe("wait duration in seconds (fractional values allowed, at most 120, default 60)")
|
|
18
18
|
}
|
|
19
19
|
}, async (args) => {
|
|
20
20
|
await new Promise((resolve) => setTimeout(resolve, args.duration * 1000));
|
package/dst/ase-statusline.js
CHANGED
|
@@ -229,7 +229,7 @@ export default class StatuslineCommand {
|
|
|
229
229
|
}
|
|
230
230
|
catch (err) {
|
|
231
231
|
const message = err instanceof Error ? err.message : String(err);
|
|
232
|
-
throw new Error(`statusline: invalid JSON on stdin: ${message}
|
|
232
|
+
throw new Error(`statusline: invalid JSON on stdin: ${message}`, { cause: err });
|
|
233
233
|
}
|
|
234
234
|
/* normalize Copilot CLI's top-level "cwd" into the
|
|
235
235
|
"workspace.current_dir" structure shared with Anthropic Claude Code CLI */
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Agentic Software Engineering (ASE)
|
|
3
|
+
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
|
+
*/
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import { execaSync } from "execa";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { writeStdout } from "./ase-stdio.js";
|
|
11
|
+
/* the repository-root-relative path components of the base directory
|
|
12
|
+
which holds all ASE-managed Git worktrees */
|
|
13
|
+
const baseComponents = [".ase", "worktree"];
|
|
14
|
+
/* reusable functionality: safe resolution of the ASE worktree
|
|
15
|
+
directories under <repo-root>/.ase/worktree/<id> */
|
|
16
|
+
export class Worktree {
|
|
17
|
+
/* validate a worktree id to keep it safe both as a single path
|
|
18
|
+
component and as the Git branch name derived from it */
|
|
19
|
+
static validateId(id) {
|
|
20
|
+
if (typeof id !== "string" || id.length === 0)
|
|
21
|
+
throw new Error("worktree: id must be a non-empty string");
|
|
22
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id))
|
|
23
|
+
throw new Error("worktree: id must match [A-Za-z0-9_-]+");
|
|
24
|
+
}
|
|
25
|
+
/* determine the fully resolved top-level directory of the Git
|
|
26
|
+
working tree, as a Git worktree can only be created from
|
|
27
|
+
inside one */
|
|
28
|
+
static repoRoot() {
|
|
29
|
+
let top = "";
|
|
30
|
+
try {
|
|
31
|
+
top = execaSync("git", ["rev-parse", "--show-toplevel"], { stderr: "ignore" }).stdout.trim();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* not inside a Git working tree */
|
|
35
|
+
}
|
|
36
|
+
if (top === "")
|
|
37
|
+
throw new Error("worktree: not inside a Git working tree -- cannot create a worktree");
|
|
38
|
+
return fs.realpathSync(top);
|
|
39
|
+
}
|
|
40
|
+
/* assert that an already existing path is a real, non-aliased
|
|
41
|
+
directory: "git worktree add" silently follows a symlinked path
|
|
42
|
+
component, so a repository carrying a committed ".ase" or
|
|
43
|
+
".ase/worktree" symlink would make it write outside the
|
|
44
|
+
repository entirely */
|
|
45
|
+
static assertRealDir(dir) {
|
|
46
|
+
let st;
|
|
47
|
+
try {
|
|
48
|
+
st = fs.lstatSync(dir);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* a not yet existing path component is created later on */
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (st.isSymbolicLink())
|
|
55
|
+
throw new Error(`worktree: "${dir}" is a symbolic link -- ` +
|
|
56
|
+
"refusing to create a worktree through it");
|
|
57
|
+
if (!st.isDirectory())
|
|
58
|
+
throw new Error(`worktree: "${dir}" exists but is not a directory`);
|
|
59
|
+
const real = fs.realpathSync(dir);
|
|
60
|
+
if (real !== dir)
|
|
61
|
+
throw new Error(`worktree: "${dir}" resolves to "${real}" -- ` +
|
|
62
|
+
"refusing to create a worktree through an aliased path");
|
|
63
|
+
}
|
|
64
|
+
/* resolve the base directory holding all ASE worktrees, asserting
|
|
65
|
+
that every path component below the repository root is a real
|
|
66
|
+
directory; the base directory is created on demand only */
|
|
67
|
+
static baseDir(create = false) {
|
|
68
|
+
let dir = Worktree.repoRoot();
|
|
69
|
+
for (const component of baseComponents) {
|
|
70
|
+
dir = path.join(dir, component);
|
|
71
|
+
Worktree.assertRealDir(dir);
|
|
72
|
+
}
|
|
73
|
+
if (create) {
|
|
74
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
75
|
+
Worktree.assertRealDir(dir);
|
|
76
|
+
}
|
|
77
|
+
return dir;
|
|
78
|
+
}
|
|
79
|
+
/* resolve the worktree directory of a single id; the leaf itself is
|
|
80
|
+
created by "git worktree add" and hence only has to be free of an
|
|
81
|
+
aliasing entry left behind by an earlier run */
|
|
82
|
+
static dir(id, create = false) {
|
|
83
|
+
Worktree.validateId(id);
|
|
84
|
+
const dir = path.join(Worktree.baseDir(create), id);
|
|
85
|
+
Worktree.assertRealDir(dir);
|
|
86
|
+
return dir;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/* CLI command "ase worktree" */
|
|
90
|
+
export default class WorktreeCommand {
|
|
91
|
+
/* register commands */
|
|
92
|
+
register(program) {
|
|
93
|
+
/* register CLI top-level command "ase worktree" */
|
|
94
|
+
const worktree = program
|
|
95
|
+
.command("worktree")
|
|
96
|
+
.description("Safely resolve the ASE worktree directories under <repo-root>/.ase/worktree")
|
|
97
|
+
.action(() => {
|
|
98
|
+
worktree.outputHelp();
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
/* register CLI sub-command "ase worktree base" */
|
|
102
|
+
worktree
|
|
103
|
+
.command("base")
|
|
104
|
+
.description("Print the validated base directory holding all ASE worktrees")
|
|
105
|
+
.option("-c, --create", "create the base directory if it does not exist yet")
|
|
106
|
+
.action(async (opts) => {
|
|
107
|
+
await writeStdout(`${Worktree.baseDir(opts.create ?? false)}\n`);
|
|
108
|
+
});
|
|
109
|
+
/* register CLI sub-command "ase worktree path" */
|
|
110
|
+
worktree
|
|
111
|
+
.command("path")
|
|
112
|
+
.description("Print the validated worktree directory of a single <id>")
|
|
113
|
+
.argument("<id>", "Worktree identifier")
|
|
114
|
+
.option("-c, --create", "create the base directory if it does not exist yet")
|
|
115
|
+
.action(async (id, opts) => {
|
|
116
|
+
await writeStdout(`${Worktree.dir(id, opts.create ?? false)}\n`);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/* render a caught error as an MCP tool error result */
|
|
121
|
+
const mcpToolError = (err) => ({
|
|
122
|
+
isError: true,
|
|
123
|
+
content: [{ type: "text", text: `ERROR: ${err instanceof Error ? err.message : String(err)}` }]
|
|
124
|
+
});
|
|
125
|
+
/* MCP registration entry point for worktree tools */
|
|
126
|
+
export class WorktreeMCP {
|
|
127
|
+
register(mcp) {
|
|
128
|
+
mcp.registerTool("ase_worktree_path", {
|
|
129
|
+
title: "ASE worktree path",
|
|
130
|
+
description: "Resolve the absolute, validated directory of an ASE-managed Git worktree. " +
|
|
131
|
+
"Returns `<repo-root>/.ase/worktree/<id>` as `text` if `id` is given, and the " +
|
|
132
|
+
"base directory `<repo-root>/.ase/worktree` otherwise. " +
|
|
133
|
+
"You MUST call this to obtain the directory of every worktree instead of " +
|
|
134
|
+
"assembling the path yourself: it rejects a path leading through a symbolic " +
|
|
135
|
+
"link, through a non-directory, or out of the repository, which `git worktree " +
|
|
136
|
+
"add` would otherwise silently follow and thereby write outside the repository. " +
|
|
137
|
+
"Set `create` to `true` to also create the base directory. " +
|
|
138
|
+
"Fails with an error if the path is unsafe or the current directory is not a " +
|
|
139
|
+
"Git working tree; in that case you MUST NOT create the worktree at all.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
id: z.string().optional()
|
|
142
|
+
.describe("worktree identifier (allowed characters: A-Z, a-z, 0-9, '_', '-'); " +
|
|
143
|
+
"if omitted, the base directory holding all ASE worktrees is returned"),
|
|
144
|
+
create: z.boolean().optional()
|
|
145
|
+
.describe("if true, create the base directory if it does not exist yet (default: false)")
|
|
146
|
+
}
|
|
147
|
+
}, async (args) => {
|
|
148
|
+
try {
|
|
149
|
+
const create = args.create ?? false;
|
|
150
|
+
const text = args.id !== undefined ?
|
|
151
|
+
Worktree.dir(args.id, create) :
|
|
152
|
+
Worktree.baseDir(create);
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text", text }]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
return mcpToolError(err);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
package/dst/ase.js
CHANGED
|
@@ -17,6 +17,7 @@ import ArtifactCommand from "./ase-artifact.js";
|
|
|
17
17
|
import MetaCommand from "./ase-meta.js";
|
|
18
18
|
import CompatCommand from "./ase-compat.js";
|
|
19
19
|
import DiagramCommand from "./ase-diagram.js";
|
|
20
|
+
import WorktreeCommand from "./ase-worktree.js";
|
|
20
21
|
import pkg from "../package.json" with { type: "json" };
|
|
21
22
|
/* globally initialize logger */
|
|
22
23
|
const log = new Log("ase", "info", "-");
|
|
@@ -56,6 +57,7 @@ const main = async () => {
|
|
|
56
57
|
new MetaCommand(log).register(program);
|
|
57
58
|
new CompatCommand().register(program);
|
|
58
59
|
new DiagramCommand(log).register(program);
|
|
60
|
+
new WorktreeCommand().register(program);
|
|
59
61
|
/* parse program arguments */
|
|
60
62
|
await program.parseAsync(process.argv);
|
|
61
63
|
/* gracefully terminate */
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "https://ase.tools",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "https://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.60",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -16,22 +16,22 @@
|
|
|
16
16
|
"type": "module",
|
|
17
17
|
"bin": { "ase": "bin/ase" },
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"eslint": "9.
|
|
20
|
-
"@eslint/js": "
|
|
21
|
-
"@typescript-eslint/parser": "8.
|
|
22
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
19
|
+
"eslint": "10.9.0",
|
|
20
|
+
"@eslint/js": "10.0.1",
|
|
21
|
+
"@typescript-eslint/parser": "8.67.0",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "8.67.0",
|
|
23
|
+
"typescript-eslint": "8.67.0",
|
|
23
24
|
"eslint-plugin-promise": "7.3.0",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"globals": "17.9.0",
|
|
25
|
+
"neostandard": "0.14.0-next.1",
|
|
26
|
+
"globals": "17.11.0",
|
|
27
27
|
"typescript": "6.0.3",
|
|
28
28
|
|
|
29
29
|
"@rse/stx": "1.1.6",
|
|
30
30
|
"nodemon": "3.1.14",
|
|
31
31
|
"shx": "0.4.0",
|
|
32
32
|
|
|
33
|
-
"@types/node": "26.
|
|
34
|
-
"@types/luxon": "3.7.
|
|
33
|
+
"@types/node": "26.2.0",
|
|
34
|
+
"@types/luxon": "3.7.5",
|
|
35
35
|
"@types/which": "3.0.4",
|
|
36
36
|
"@types/update-notifier": "6.0.8",
|
|
37
37
|
"@types/shell-quote": "1.7.5",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"commander": "15.0.0",
|
|
45
|
-
"@dotenvx/dotenvx": "2.
|
|
45
|
+
"@dotenvx/dotenvx": "2.21.0",
|
|
46
46
|
"yaml": "2.9.0",
|
|
47
47
|
"valibot": "1.4.2",
|
|
48
48
|
"execa": "10.0.1",
|
|
@@ -72,9 +72,8 @@
|
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
74
74
|
"npm": ">=10.0.0",
|
|
75
|
-
"node": ">=22.
|
|
75
|
+
"node": ">=22.13.0"
|
|
76
76
|
},
|
|
77
|
-
"upd": [ "!eslint", "!@eslint/js" ],
|
|
78
77
|
"files": [
|
|
79
78
|
"dst/**/*",
|
|
80
79
|
"bin/**/*",
|
|
@@ -334,6 +334,12 @@ Workflow
|
|
|
334
334
|
pure-insertion hunk at its new location - *never* by
|
|
335
335
|
deleting and re-adding the unchanged lines in between.
|
|
336
336
|
|
|
337
|
+
This minimality rule applies *only* to <old-text/> and
|
|
338
|
+
<new-text/>. It *MUST* *NOT* be understood as a reason to
|
|
339
|
+
also drop the surrounding context of the two following
|
|
340
|
+
substeps - that context is *mandatory* and is reported
|
|
341
|
+
*separately* from the changed lines.
|
|
342
|
+
|
|
337
343
|
3. Set <context-before/> to exactly *up to two* lines of
|
|
338
344
|
*unchanged* code context which occurs in <file/>
|
|
339
345
|
directly *before* <old-text/>, i.e., the lines (<line/>
|
|
@@ -365,6 +371,15 @@ Workflow
|
|
|
365
371
|
}
|
|
366
372
|
</template>
|
|
367
373
|
|
|
374
|
+
Here `line` is a *number* and all four other fields are
|
|
375
|
+
*JSON strings* carrying the *verbatim* source lines
|
|
376
|
+
(embedded newlines escaped as `\n`, no line-number
|
|
377
|
+
prefixes, original indentation preserved). The empty
|
|
378
|
+
string `""` is allowed for `context_before` and
|
|
379
|
+
`context_after` *only* at the very start or end of the
|
|
380
|
+
file - for every other hunk both *MUST* carry their
|
|
381
|
+
context lines.
|
|
382
|
+
|
|
368
383
|
3. If <change-set/> is not empty, set
|
|
369
384
|
<change-set><change-set/>,</change-set> (append a comma).
|
|
370
385
|
Then append the following <template/> to <change-set/>:
|
|
@@ -50,6 +50,12 @@ Workflow
|
|
|
50
50
|
as *two separate* problems instead of one large change
|
|
51
51
|
which re-states the unchanged lines.
|
|
52
52
|
|
|
53
|
+
This minimality rule applies *only* to <old-text/> and
|
|
54
|
+
<new-text/>. It *MUST* *NOT* be understood as a reason to
|
|
55
|
+
also drop the surrounding context of the substeps 5 and 6
|
|
56
|
+
below - that context is *mandatory* and is reported
|
|
57
|
+
*separately* from the changed lines.
|
|
58
|
+
|
|
53
59
|
4. Set <description/> to an ultra-brief and concise
|
|
54
60
|
Markdown-formatted description of the problem with
|
|
55
61
|
a hint of what is wrong and why it is wrong. In
|
|
@@ -94,6 +100,15 @@ Workflow
|
|
|
94
100
|
}
|
|
95
101
|
</template>
|
|
96
102
|
|
|
103
|
+
Here `line` is a *number* and all other fields are *JSON
|
|
104
|
+
strings*, where <context-before/>, <old-text/>, <new-text/>,
|
|
105
|
+
and <context-after/> carry the *verbatim* document lines
|
|
106
|
+
(embedded newlines escaped as `\n`, no line-number prefixes,
|
|
107
|
+
original indentation preserved). The empty string `""` is
|
|
108
|
+
allowed for `context_before` and `context_after` *only* at
|
|
109
|
+
the very start or end of the document - for every other
|
|
110
|
+
problem both *MUST* carry their context lines.
|
|
111
|
+
|
|
97
112
|
3. You *MUST* *NOT* propose, apply, or render any document
|
|
98
113
|
changes yourself. Instead, return *exclusively* as the last message
|
|
99
114
|
a single JSON block (no markdown, no prose, no preamble, no summary)
|
|
@@ -138,7 +138,7 @@ Set <args>--int-reuse-task</args>.
|
|
|
138
138
|
<template/> and then *STOP*. Do *not* implement the plan.
|
|
139
139
|
|
|
140
140
|
<template>
|
|
141
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**,
|
|
141
|
+
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ▶ status: **plan finalized -- done**
|
|
142
142
|
</template>
|
|
143
143
|
</if>
|
|
144
144
|
|
|
@@ -83,16 +83,14 @@ Task Skill Common Steps
|
|
|
83
83
|
|
|
84
84
|
- If <text/> starts with `ERROR:` or `WARNING:`:
|
|
85
85
|
Set <task-content></task-content> (set task content to empty).
|
|
86
|
-
Set <words/> to "0".
|
|
87
86
|
|
|
88
87
|
- If <text/> does NOT start with `ERROR:` and NOT with `WARNING:`:
|
|
89
88
|
Set <task-content><text/></task-content> (set task content to text).
|
|
90
|
-
Calculate the number of words <words/> of <task-content/>.
|
|
91
89
|
|
|
92
90
|
Only output the following <template/>:
|
|
93
91
|
|
|
94
92
|
<template>
|
|
95
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**,
|
|
93
|
+
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ▶ status: **<status/>**
|
|
96
94
|
</template>
|
|
97
95
|
|
|
98
96
|
</define>
|
|
@@ -109,16 +107,18 @@ had no `Created:` frontmatter key), set
|
|
|
109
107
|
(fall back to the modified timestamp). Re-insert the current
|
|
110
108
|
<ase-task-id/>, the original <timestamp-created/>, and the
|
|
111
109
|
refreshed <timestamp-modified/> into the frontmatter keys `Id:`,
|
|
112
|
-
`Created:`, and `Modified:` of <task-content
|
|
113
|
-
the number of words <words/> of <task-content/>.
|
|
110
|
+
`Created:`, and `Modified:` of <task-content/>.
|
|
114
111
|
|
|
115
112
|
Call the `ase_task_save(id: "<ase-task-id/>", text:
|
|
116
113
|
"<task-content/>")` tool of the `ase` MCP server to save the task
|
|
117
|
-
plan content in its *authoring form*.
|
|
114
|
+
plan content in its *authoring form*. This `ase_task_save` MCP
|
|
115
|
+
tool call is the *only* permitted way to persist the task plan --
|
|
116
|
+
you *MUST* *NEVER* write the plan file via `Write`/`Edit` or by
|
|
117
|
+
executing a shell command. Do not output anything
|
|
118
118
|
related to this MCP call except the following <template/>:
|
|
119
119
|
|
|
120
120
|
<template>
|
|
121
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**,
|
|
121
|
+
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ▶ status: **<arg1/>**
|
|
122
122
|
</template>
|
|
123
123
|
|
|
124
124
|
</define>
|
|
@@ -169,7 +169,7 @@ Only output the following <template/> and then call the tool
|
|
|
169
169
|
stop processing the current skill once the `Skill` tool was used.
|
|
170
170
|
|
|
171
171
|
<template>
|
|
172
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**,
|
|
172
|
+
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ▶ status: **<arg2/>**
|
|
173
173
|
</template>
|
|
174
174
|
|
|
175
175
|
</define>
|
|
@@ -99,10 +99,18 @@ Control Flow Constructs
|
|
|
99
99
|
<sleep duration="<sleep-duration/>"/>:
|
|
100
100
|
|
|
101
101
|
This specifies a single *wait* of <sleep-duration/> seconds
|
|
102
|
-
(fractional values like `1.5` are allowed)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
(fractional values like `1.5` are allowed).
|
|
103
|
+
|
|
104
|
+
The wait is performed with the `ase_sleep(duration:
|
|
105
|
+
<chunk-duration/>)` tool of the `ase` MCP server, which returns once
|
|
106
|
+
<chunk-duration/> has elapsed. To stay safely inside the tool's own
|
|
107
|
+
duration limit, <chunk-duration/> is *never* above `60`: a
|
|
108
|
+
<sleep-duration/> above `60` is therefore *split* into consecutive
|
|
109
|
+
calls -- as many `60` second calls as fit, followed by one final
|
|
110
|
+
call carrying the remaining seconds -- so the calls sum up to
|
|
111
|
+
exactly <sleep-duration/>.
|
|
112
|
+
|
|
113
|
+
This construct is expanded into nothing. Do not output anything.
|
|
106
114
|
|
|
107
115
|
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
108
116
|
<await condition="<await-condition/>" [interval="<await-interval/>"]><await-body/></await>:
|
|
@@ -111,10 +119,20 @@ Control Flow Constructs
|
|
|
111
119
|
<await-condition/> is met: if <await-condition/> is met, the
|
|
112
120
|
<await-body/> is executed once and the construct is finished. If
|
|
113
121
|
<await-condition/> is *not* met, wait for <await-interval/> seconds
|
|
114
|
-
(default: `60`)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
(default: `60`) and then *start over* by re-evaluating
|
|
123
|
+
<await-condition/>.
|
|
124
|
+
|
|
125
|
+
A single wait is performed with the `ase_sleep(duration:
|
|
126
|
+
<chunk-duration/>)` tool of the `ase` MCP server. To stay safely
|
|
127
|
+
inside the tool's own duration limit, <chunk-duration/> is *never*
|
|
128
|
+
above `60`: an <await-interval/> above `60` is therefore *split* into
|
|
129
|
+
consecutive calls -- as many `60` second calls as fit, followed by
|
|
130
|
+
one final call carrying the remaining seconds -- so the calls sum up
|
|
131
|
+
to exactly <await-interval/>. You *MUST* *NOT* re-evaluate
|
|
132
|
+
<await-condition/> between the calls of one such wait.
|
|
133
|
+
|
|
134
|
+
This construct is expanded to its <await-body/>. Do not output
|
|
135
|
+
anything else.
|
|
118
136
|
|
|
119
137
|
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
120
138
|
<agent <attr/>="<value/>" [...]><agent-body/></agent>:
|
|
@@ -102,8 +102,10 @@ following procedure:
|
|
|
102
102
|
</ase-tpl-boxed>
|
|
103
103
|
</text>
|
|
104
104
|
|
|
105
|
-
If <n/> is less than 2
|
|
106
|
-
|
|
105
|
+
If <n/> is less than 2 -- or less than 1 when <opts/> contains
|
|
106
|
+
`--other` and does *not* contain `--no-other`, as the free-text
|
|
107
|
+
path then complements a single answer option:
|
|
108
|
+
Set <result>ERROR: custom-dialog requires 2-9 (with free-text: 1-9) answer lines, got <n/></result>
|
|
107
109
|
and *SKIP* the following step 2.2 and continue with step 2.3 dispatch.
|
|
108
110
|
|
|
109
111
|
2. Output the following <template/>, end the current turn, wait for the
|
package/plugin/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "https://ase.tools",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "https://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.60",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
"@rse/stx": "1.1.6",
|
|
18
18
|
"markdownlint": "0.41.1",
|
|
19
19
|
"markdownlint-cli2": "0.23.2",
|
|
20
|
-
"eslint": "10.
|
|
20
|
+
"eslint": "10.9.0",
|
|
21
21
|
"@eslint/markdown": "8.0.3",
|
|
22
|
-
"eslint-markdown": "0.
|
|
22
|
+
"eslint-markdown": "0.14.0"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"npm": ">=10.0.0",
|
|
26
|
-
"node": ">=22.
|
|
26
|
+
"node": ">=22.13.0"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"start": "stx -v4 -l warning -c etc/stx.conf"
|