@rse/ase 0.9.56 → 0.9.57
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-config.js +72 -31
- package/dst/ase-getopt.js +3 -1
- package/dst/ase-hook.js +5 -3
- package/dst/ase-markdown.js +7 -4
- package/dst/ase-service.js +1 -1
- package/dst/ase-task.js +1 -0
- package/package.json +1 -1
- 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/meta/ase-common-code.md +4 -0
- package/plugin/meta/ase-common-task.md +17 -0
- package/plugin/meta/ase-format-arch.md +0 -14
- package/plugin/meta/ase-format-meta.md +15 -2
- package/plugin/meta/ase-format-spec.md +0 -14
- package/plugin/package.json +1 -1
- package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
- package/plugin/skills/ase-arch-discover/SKILL.md +1 -1
- package/plugin/skills/ase-code-resolve/SKILL.md +45 -19
- package/plugin/skills/ase-code-resolve/help.md +15 -4
- package/plugin/skills/ase-meta-changelog/SKILL.md +1 -0
- package/plugin/skills/ase-meta-chat/SKILL.md +9 -0
- package/plugin/skills/ase-meta-config/help.md +2 -1
- package/plugin/skills/ase-task-preflight/SKILL.md +4 -26
- package/plugin/skills/ase-task-reboot/SKILL.md +6 -39
package/dst/ase-config.js
CHANGED
|
@@ -124,27 +124,33 @@ const gitToplevel = () => {
|
|
|
124
124
|
gitToplevelCache.set(cwd, top);
|
|
125
125
|
return top === "" ? null : top;
|
|
126
126
|
};
|
|
127
|
-
/*
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
/* determine the project root directory, i.e. either the top-level
|
|
128
|
+
directory of the Git working tree or the nearest directory at or
|
|
129
|
+
above cwd which carries a ".ase" directory */
|
|
130
|
+
const projectRoot = () => {
|
|
131
|
+
const top = gitToplevel();
|
|
132
|
+
if (top !== null)
|
|
133
|
+
return top;
|
|
132
134
|
let dir = fs.realpathSync(process.cwd());
|
|
133
135
|
for (;;) {
|
|
134
136
|
if (fs.existsSync(path.join(dir, ".ase")))
|
|
135
|
-
return
|
|
137
|
+
return dir;
|
|
136
138
|
const parent = path.dirname(dir);
|
|
137
139
|
if (parent === dir)
|
|
138
|
-
return
|
|
140
|
+
return null;
|
|
139
141
|
dir = parent;
|
|
140
142
|
}
|
|
141
143
|
};
|
|
144
|
+
/* detect whether a project context exists, i.e. either we are inside
|
|
145
|
+
a Git working tree or a ".ase" directory is present at or above cwd */
|
|
146
|
+
const hasProjectContext = () => projectRoot() !== null;
|
|
142
147
|
/* parse a raw "--scope" option value into a canonical Scope chain;
|
|
143
148
|
accepts a comma-separated list of terms in any order. The "user"
|
|
144
149
|
term is always implicitly added at the bottom of the chain; the
|
|
145
150
|
"project" term is implicitly added only when a project context
|
|
146
|
-
exists (Git repository or ".ase" directory at or above cwd)
|
|
147
|
-
|
|
151
|
+
exists (Git repository or ".ase" directory at or above cwd) and it
|
|
152
|
+
stays weaker than the strongest explicitly requested term, and an
|
|
153
|
+
explicit "project" term requires that same context */
|
|
148
154
|
export const parseScope = (value) => {
|
|
149
155
|
const projectActive = hasProjectContext();
|
|
150
156
|
const input = (value === undefined || value === "") ?
|
|
@@ -162,7 +168,11 @@ export const parseScope = (value) => {
|
|
|
162
168
|
if (seen.has("project") && !projectActive)
|
|
163
169
|
throw new Error("invalid --scope: \"project\" requires a project context " +
|
|
164
170
|
"(a Git repository or a \".ase\" directory at or above the current directory)");
|
|
165
|
-
|
|
171
|
+
/* the strongest term of the chain is the write target, so an implicitly
|
|
172
|
+
added "project" term must never outrank the strongest explicitly
|
|
173
|
+
requested term, as this would silently retarget the caller's request */
|
|
174
|
+
const rankMax = Math.max(...terms.map((t) => scopeRank(t.kind)));
|
|
175
|
+
if (!seen.has("project") && projectActive && rankMax > scopeRank("project"))
|
|
166
176
|
terms.unshift({ kind: "project" });
|
|
167
177
|
if (!seen.has("user"))
|
|
168
178
|
terms.unshift({ kind: "user" });
|
|
@@ -210,6 +220,7 @@ export class Config {
|
|
|
210
220
|
log;
|
|
211
221
|
docs;
|
|
212
222
|
target;
|
|
223
|
+
pruned;
|
|
213
224
|
/* creation */
|
|
214
225
|
constructor(name, schema, log, scope = [{ kind: "user" }, { kind: "project" }]) {
|
|
215
226
|
if (scope.length === 0)
|
|
@@ -222,6 +233,7 @@ export class Config {
|
|
|
222
233
|
this.filename = this.resolveFilename(name, tgt);
|
|
223
234
|
this.docs = [{ scope: tgt, filename: this.filename, doc: new Document() }];
|
|
224
235
|
this.target = 0;
|
|
236
|
+
this.pruned = [];
|
|
225
237
|
}
|
|
226
238
|
/* render a scope term as a short textual label */
|
|
227
239
|
static scopeLabel(term) {
|
|
@@ -252,16 +264,13 @@ export class Config {
|
|
|
252
264
|
return path.join(this.userConfigDir(), `${name}.yaml`);
|
|
253
265
|
else if (term.kind === "project") {
|
|
254
266
|
const rel = path.join(".ase", `${name}.yaml`);
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
this.findUpward(cwd, top, rel) :
|
|
259
|
-
(fs.existsSync(path.join(cwd, rel)) ? path.join(cwd, rel) : null);
|
|
260
|
-
return found ?? path.join(top ?? cwd, rel);
|
|
267
|
+
const root = projectRoot() ?? process.cwd();
|
|
268
|
+
const found = this.findUpward(process.cwd(), root, rel);
|
|
269
|
+
return found ?? path.join(root, rel);
|
|
261
270
|
}
|
|
262
271
|
else if (term.kind === "task") {
|
|
263
|
-
const
|
|
264
|
-
return path.join(
|
|
272
|
+
const root = projectRoot() ?? process.cwd();
|
|
273
|
+
return path.join(root, ".ase", "task", term.id, `${name}.yaml`);
|
|
265
274
|
}
|
|
266
275
|
else
|
|
267
276
|
return path.join(os.homedir(), ".ase", "session", term.id, `${name}.yaml`);
|
|
@@ -290,6 +299,7 @@ export class Config {
|
|
|
290
299
|
read(mode = "lenient") {
|
|
291
300
|
const chain = this.scope;
|
|
292
301
|
const docs = [];
|
|
302
|
+
this.pruned = [];
|
|
293
303
|
for (let i = 0; i < chain.length; i++) {
|
|
294
304
|
const sc = chain[i];
|
|
295
305
|
if (sc.kind === "default") {
|
|
@@ -321,6 +331,8 @@ export class Config {
|
|
|
321
331
|
if (perDocMode === "strict")
|
|
322
332
|
throw new Error(msg);
|
|
323
333
|
this.log.write("warning", msg);
|
|
334
|
+
if (isTarget)
|
|
335
|
+
this.pruned.push(`unparsable YAML (${doc.errors[0].message.split("\n")[0]})`);
|
|
324
336
|
doc = new Document();
|
|
325
337
|
}
|
|
326
338
|
docs.push({ scope: sc, filename, doc });
|
|
@@ -330,7 +342,9 @@ export class Config {
|
|
|
330
342
|
for (let i = 0; i < docs.length; i++) {
|
|
331
343
|
const isTarget = (i === this.target);
|
|
332
344
|
const perDocMode = isTarget ? mode : "lenient";
|
|
333
|
-
this.validateDoc(docs[i].doc, docs[i].filename, perDocMode);
|
|
345
|
+
const removed = this.validateDoc(docs[i].doc, docs[i].filename, perDocMode);
|
|
346
|
+
if (isTarget)
|
|
347
|
+
this.pruned.push(...removed);
|
|
334
348
|
}
|
|
335
349
|
}
|
|
336
350
|
/* acquire a cross-process advisory lock on the target scope's file,
|
|
@@ -355,18 +369,28 @@ export class Config {
|
|
|
355
369
|
const td = this.docs[this.target];
|
|
356
370
|
if (td.scope.kind === "default")
|
|
357
371
|
throw new Error("internal error: \"default\" scope is not writable");
|
|
372
|
+
/* a lenient read physically removes the invalid content from the in-memory
|
|
373
|
+
target document, so writing that document back would silently erase the
|
|
374
|
+
very same content from the file on disk */
|
|
375
|
+
if (this.pruned.length > 0)
|
|
376
|
+
throw new Error(`refusing to overwrite ${td.filename}: it carries invalid content ` +
|
|
377
|
+
`(${this.pruned.join(", ")}) which was skipped on reading and hence would be ` +
|
|
378
|
+
"lost on writing -- repair the file first");
|
|
358
379
|
this.validateDoc(td.doc, td.filename, "strict");
|
|
359
380
|
fs.mkdirSync(path.dirname(td.filename), { recursive: true });
|
|
360
381
|
writeFileAtomic.sync(td.filename, td.doc.toString({ indent: 4 }), { encoding: "utf8" });
|
|
361
382
|
}
|
|
362
|
-
/* validate a single YAML document against the optional schema
|
|
383
|
+
/* validate a single YAML document against the optional schema; in "strict"
|
|
384
|
+
mode all invalid entries are reported as a thrown error, in "lenient" mode
|
|
385
|
+
they are removed from the document and their dotted paths are returned */
|
|
363
386
|
validateDoc(doc, filename, mode = "strict") {
|
|
364
387
|
if (this.schema === null)
|
|
365
|
-
return;
|
|
388
|
+
return [];
|
|
389
|
+
const removed = [];
|
|
366
390
|
for (;;) {
|
|
367
391
|
const result = v.safeParse(this.schema, doc.toJS());
|
|
368
392
|
if (result.success)
|
|
369
|
-
return;
|
|
393
|
+
return removed;
|
|
370
394
|
if (mode === "strict") {
|
|
371
395
|
const issues = result.issues.map((i) => {
|
|
372
396
|
const dotPath = (i.path ?? []).map((p) => String(p.key)).join(".");
|
|
@@ -374,19 +398,20 @@ export class Config {
|
|
|
374
398
|
}).join("; ");
|
|
375
399
|
throw new Error(`invalid configuration in ${filename}: ${issues}`);
|
|
376
400
|
}
|
|
377
|
-
|
|
401
|
+
const before = removed.length;
|
|
378
402
|
for (const i of result.issues) {
|
|
379
403
|
const segs = (i.path ?? []).map((p) => String(p.key));
|
|
380
404
|
const dotPath = segs.join(".");
|
|
381
405
|
this.log.write("warning", `invalid entry in ${filename}: ${dotPath ? `${dotPath}: ` : ""}${i.message}`);
|
|
382
|
-
if (segs.length > 0)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
406
|
+
if (segs.length > 0 && doc.deleteIn(segs))
|
|
407
|
+
removed.push(dotPath);
|
|
408
|
+
/* issues at the document root and issues whose stringified path does not
|
|
409
|
+
address a deletable node (e.g. a non-string YAML key like "404:", which
|
|
410
|
+
"toJS" stringifies) cannot be removed; processing continues with the
|
|
411
|
+
remaining issues */
|
|
387
412
|
}
|
|
388
|
-
if (
|
|
389
|
-
return;
|
|
413
|
+
if (removed.length === before)
|
|
414
|
+
return removed;
|
|
390
415
|
}
|
|
391
416
|
}
|
|
392
417
|
/* enumerate all full dotted leaf paths from the attached valibot schema */
|
|
@@ -444,6 +469,21 @@ export class Config {
|
|
|
444
469
|
}
|
|
445
470
|
return undefined;
|
|
446
471
|
}
|
|
472
|
+
/* retrieve the explicitly configured value at a dotted key, i.e. the same
|
|
473
|
+
cascade as "get", but skipping the built-in "default" scope layer, so
|
|
474
|
+
callers can distinguish a deliberately configured value from a merely
|
|
475
|
+
preset one */
|
|
476
|
+
getExplicit(key) {
|
|
477
|
+
const segs = this.resolveKey(key).split(".");
|
|
478
|
+
for (let i = this.docs.length - 1; i >= 0; i--) {
|
|
479
|
+
if (this.docs[i].scope.kind === "default")
|
|
480
|
+
continue;
|
|
481
|
+
const node = this.docs[i].doc.getIn(segs);
|
|
482
|
+
if (node !== undefined)
|
|
483
|
+
return node;
|
|
484
|
+
}
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
447
487
|
/* enumerate the effective leaf entries across the full scope chain;
|
|
448
488
|
each returned entry identifies the originating scope */
|
|
449
489
|
entries() {
|
|
@@ -551,7 +591,8 @@ export default class ConfigCommand {
|
|
|
551
591
|
.option("--scope <scope>", "configuration scope chain: comma-separated list of \"user\", \"project\", " +
|
|
552
592
|
"\"task:<id>\", and/or \"session:<id>\" terms (e.g. \"task:N,session:M\"); " +
|
|
553
593
|
"\"user\" is always implicitly included and \"project\" is implicitly " +
|
|
554
|
-
"included whenever a project context (Git repo or upward \".ase\" directory) exists"
|
|
594
|
+
"included whenever a project context (Git repo or upward \".ase\" directory) exists, " +
|
|
595
|
+
"but never above the strongest explicitly requested term")
|
|
555
596
|
.description("manage ASE configuration")
|
|
556
597
|
.action(() => {
|
|
557
598
|
configCmd.outputHelp();
|
package/dst/ase-getopt.js
CHANGED
|
@@ -7,7 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
import { Command, Option } from "commander";
|
|
8
8
|
import { parse as shParse, quote as shQuote } from "shell-quote";
|
|
9
9
|
/* tokenize a raw input string into [start,end) token ranges, preserving
|
|
10
|
-
the quoting so the original text can later be sliced verbatim */
|
|
10
|
+
the quoting and escaping so the original text can later be sliced verbatim */
|
|
11
11
|
const tokenizeRanges = (input) => {
|
|
12
12
|
const ranges = [];
|
|
13
13
|
let i = 0;
|
|
@@ -30,6 +30,8 @@ const tokenizeRanges = (input) => {
|
|
|
30
30
|
if (i < input.length)
|
|
31
31
|
i++;
|
|
32
32
|
}
|
|
33
|
+
else if (ch === "\\" && i + 1 < input.length)
|
|
34
|
+
i += 2;
|
|
33
35
|
else
|
|
34
36
|
i++;
|
|
35
37
|
}
|
package/dst/ase-hook.js
CHANGED
|
@@ -240,10 +240,12 @@ export default class HookCommand {
|
|
|
240
240
|
const projectId = path.basename(projectDir);
|
|
241
241
|
/* determine user id */
|
|
242
242
|
const userId = process.env.USER ?? process.env.LOGNAME ?? "unknown";
|
|
243
|
-
/* helper function: determine a setting from the
|
|
244
|
-
falling back to an environment variable and a default
|
|
243
|
+
/* helper function: determine a setting from the explicitly configured
|
|
244
|
+
scopes, falling back to an environment variable and a default; the
|
|
245
|
+
built-in "default" scope layer is deliberately skipped, as its preset
|
|
246
|
+
value is always present and would hence shadow the environment variable */
|
|
245
247
|
const setting = (key, envVar, dflt) => {
|
|
246
|
-
const val = cfg.
|
|
248
|
+
const val = cfg.getExplicit(key);
|
|
247
249
|
return typeof val === "string" ? val : (process.env[envVar] ?? dflt);
|
|
248
250
|
};
|
|
249
251
|
/* determine agent persona style, agent guidance level, and project boxing transparency */
|
package/dst/ase-markdown.js
CHANGED
|
@@ -19,9 +19,12 @@ export class Markdown {
|
|
|
19
19
|
/* segment the input line-wise into alternating non-fenced and
|
|
20
20
|
fenced regions: a fenced code block opens with a line whose first
|
|
21
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;
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
closes with a matching-or-longer run of the same marker; for a
|
|
23
|
+
backtick fence the info string trailing the run must not carry any
|
|
24
|
+
further backtick, so an inline code span delimited by a run of 3+
|
|
25
|
+
backticks never opens a fence; fenced regions are emitted verbatim
|
|
26
|
+
while only non-fenced regions are handed to the rewriting passes
|
|
27
|
+
below */
|
|
25
28
|
const lines = text.split("\n");
|
|
26
29
|
let result = "";
|
|
27
30
|
let buf = "";
|
|
@@ -37,7 +40,7 @@ export class Markdown {
|
|
|
37
40
|
for (let li = 0; li < lines.length; li++) {
|
|
38
41
|
const line = lines[li];
|
|
39
42
|
const nl = li < lines.length - 1 ? "\n" : "";
|
|
40
|
-
const m = line.match(/^\s*(`{3,}|~{3,})/);
|
|
43
|
+
const m = line.match(/^\s*(`{3,}(?![^`]*`)|~{3,})/);
|
|
41
44
|
if (!inFence && m) {
|
|
42
45
|
/* a fence-opening line: flush the pending non-fenced buffer,
|
|
43
46
|
enter fenced mode, and emit the opener verbatim */
|
package/dst/ase-service.js
CHANGED
|
@@ -439,7 +439,7 @@ export default class ServiceCommand {
|
|
|
439
439
|
return await new Promise(() => { });
|
|
440
440
|
}
|
|
441
441
|
if (port !== null) {
|
|
442
|
-
const match = await probe(port, ctx.projectId);
|
|
442
|
+
const match = await probe(port, ctx.projectId).catch(() => null);
|
|
443
443
|
if (match === true) {
|
|
444
444
|
this.log.write("info", `service: already running on port ${port}`);
|
|
445
445
|
return 0;
|
package/dst/ase-task.js
CHANGED
|
@@ -334,6 +334,7 @@ export class Task {
|
|
|
334
334
|
/* set the active task id for a given session */
|
|
335
335
|
static setId(log, session, id) {
|
|
336
336
|
Task.validateSession(session);
|
|
337
|
+
Task.validateId(id);
|
|
337
338
|
const scope = parseScope(`session:${session}`);
|
|
338
339
|
const cfg = new Config("config", configSchema, log, scope);
|
|
339
340
|
cfg.lock(() => {
|
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.57",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -194,4 +194,8 @@ Set <args>--int-reuse-task</args>.
|
|
|
194
194
|
Then call the tool `Skill(skill: "ase:ase-task-edit", args: "<args/>")`.
|
|
195
195
|
</else>
|
|
196
196
|
|
|
197
|
+
6. In every branch above which invoked the `Skill` tool, you *MUST*
|
|
198
|
+
immediately stop processing the current skill once the `Skill`
|
|
199
|
+
tool was used.
|
|
200
|
+
|
|
197
201
|
</define>
|
|
@@ -156,3 +156,20 @@ related to this MCP call except the following <template/>:
|
|
|
156
156
|
</expand>
|
|
157
157
|
|
|
158
158
|
</define>
|
|
159
|
+
|
|
160
|
+
<define name="task-next-handoff">
|
|
161
|
+
|
|
162
|
+
Set <args>--int-reuse-task</args>.
|
|
163
|
+
<if condition="<getopt-option-next/> is not equal `none`">
|
|
164
|
+
Set <args><args/> --next <getopt-option-next/></args>
|
|
165
|
+
</if>
|
|
166
|
+
Only output the following <template/> and then call the tool
|
|
167
|
+
`Skill(skill: "ase:<arg1/>", args: "<args/>")` to invoke the
|
|
168
|
+
`ase:<arg1/>` skill to continue with the updated plan. Immediately
|
|
169
|
+
stop processing the current skill once the `Skill` tool was used.
|
|
170
|
+
|
|
171
|
+
<template>
|
|
172
|
+
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **<arg2/>**
|
|
173
|
+
</template>
|
|
174
|
+
|
|
175
|
+
</define>
|
|
@@ -18,20 +18,6 @@ at `01`) according to the order of the **Artifact** list below, and with
|
|
|
18
18
|
Pascal-casing each word (upper-casing its first letter) and using `-`
|
|
19
19
|
characters instead of spaces (e.g. `Context-View`).
|
|
20
20
|
|
|
21
|
-
Each **Artifact** file *MUST* begin with a single blank line before its
|
|
22
|
-
`#` heading and end with a single blank line after its last content line
|
|
23
|
-
(followed by the trailing newline), mirroring the blank lines shown
|
|
24
|
-
inside the `<format>` blocks below.
|
|
25
|
-
|
|
26
|
-
Each **Artifact** contains two timestamps: the <timestamp-created/>
|
|
27
|
-
is the timestamp when this **Artifact** was created. The
|
|
28
|
-
<timestamp-modified/> is the timestamp when this **Artifact** was last
|
|
29
|
-
modified. Both use an ISO-style format value. The value of both can be
|
|
30
|
-
determined by a call to the `ase_timestamp(format: "yyyy-LL-dd HH:mm")`
|
|
31
|
-
tool of the `ase` MCP server, using the `text` field of its response.
|
|
32
|
-
Whenever an **Artifact** is updated, the <timestamp-modified/> *MUST* be
|
|
33
|
-
updated, too.
|
|
34
|
-
|
|
35
21
|
The **Artifact Set** **Architecture (ARCH)** consists of the following
|
|
36
22
|
distinct **Artifact**s (listed under their <artifact-name/> and their
|
|
37
23
|
<artifact-id/>):
|
|
@@ -51,8 +51,7 @@ Artifact Meta Information
|
|
|
51
51
|
name (for stable ordering) and is *not* part of the **Artifact**'s
|
|
52
52
|
or its **Aspect**s' identifiers.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
blank line. All its lines should be kept below 140 characters,
|
|
54
|
+
All lines of an **Artifact** should be kept below 140 characters,
|
|
56
55
|
whenever possible by line-breaking with newlines after about 120
|
|
57
56
|
characters per line.
|
|
58
57
|
|
|
@@ -64,6 +63,20 @@ Artifact Meta Information
|
|
|
64
63
|
characters and in total not longer than 30 characters). An example
|
|
65
64
|
is `user-login`.
|
|
66
65
|
|
|
66
|
+
Each **Artifact** file *MUST* begin with a single blank line before its
|
|
67
|
+
`#` heading and end with a single blank line after its last content line
|
|
68
|
+
(followed by the trailing newline), mirroring the blank lines shown
|
|
69
|
+
inside the `<format>` blocks below.
|
|
70
|
+
|
|
71
|
+
Each **Artifact** contains two timestamps: the <timestamp-created/>
|
|
72
|
+
is the timestamp when this **Artifact** was created. The
|
|
73
|
+
<timestamp-modified/> is the timestamp when this **Artifact** was last
|
|
74
|
+
modified. Both use an ISO-style format value. The value of both can be
|
|
75
|
+
determined by a call to the `ase_timestamp(format: "yyyy-LL-dd HH:mm")`
|
|
76
|
+
tool of the `ase` MCP server, using the `text` field of its response.
|
|
77
|
+
Whenever an **Artifact** is updated, the <timestamp-modified/> *MUST* be
|
|
78
|
+
updated, too.
|
|
79
|
+
|
|
67
80
|
An **Artifact** *MAY* additionally declare an **Export** -- a derived,
|
|
68
81
|
ready-to-consume rendering of (part of) its content, materialized as a
|
|
69
82
|
*side-by-side* file next to the **Artifact** itself. An **Artifact**
|
|
@@ -18,20 +18,6 @@ at `01`) according to the order of the **Artifact** list below, and with
|
|
|
18
18
|
Pascal-casing each word (upper-casing its first letter) and using `-`
|
|
19
19
|
characters instead of spaces (e.g. `Customer-Journey`).
|
|
20
20
|
|
|
21
|
-
Each **Artifact** file *MUST* begin with a single blank line before its
|
|
22
|
-
`#` heading and end with a single blank line after its last content line
|
|
23
|
-
(followed by the trailing newline), mirroring the blank lines shown
|
|
24
|
-
inside the `<format>` blocks below.
|
|
25
|
-
|
|
26
|
-
Each **Artifact** contains two timestamps: the <timestamp-created/>
|
|
27
|
-
is the timestamp when this **Artifact** was created. The
|
|
28
|
-
<timestamp-modified/> is the timestamp when this **Artifact** was last
|
|
29
|
-
modified. Both use an ISO-style format value. The value of both can be
|
|
30
|
-
determined by a call to the `ase_timestamp(format: "yyyy-LL-dd HH:mm")`
|
|
31
|
-
tool of the `ase` MCP server, using the `text` field of its response.
|
|
32
|
-
Whenever an **Artifact** is updated, the <timestamp-modified/> *MUST* be
|
|
33
|
-
updated, too.
|
|
34
|
-
|
|
35
21
|
The **Artifact Set** **Specification (SPEC)** consists of the following
|
|
36
22
|
distinct **Artifact**s (listed under their <artifact-name/> and their
|
|
37
23
|
<artifact-id/>):
|
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.57",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ase-arch-analyze
|
|
3
|
-
argument-hint: "[--help|-h] <source-reference>"
|
|
3
|
+
argument-hint: "[--help|-h] [--prefix|-P=<prefix>] <source-reference>"
|
|
4
4
|
description: Review software architecture, including package cohesion and inter-package coupling
|
|
5
5
|
user-invocable: true
|
|
6
6
|
disable-model-invocation: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ase-code-resolve
|
|
3
|
-
argument-hint: "[--help|-h] [--auto|-a] [--dry|-d] [--quick|-Q] [--next|-n <option>[,...]] [<task-id>:] <problem>"
|
|
3
|
+
argument-hint: "[--help|-h] [--auto|-a] [--dry|-d] [--direct|-D] [--quick|-Q] [--next|-n <option>[,...]] [<task-id>:] <problem>"
|
|
4
4
|
description: >
|
|
5
5
|
Resolve Problem:
|
|
6
6
|
Use when user wants to "bugfix" or "fix" code or "resolve" a problem.
|
|
@@ -10,6 +10,7 @@ effort: xhigh
|
|
|
10
10
|
allowed-tools:
|
|
11
11
|
- "Skill"
|
|
12
12
|
- "Agent"
|
|
13
|
+
- "Read"
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
@${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
|
|
@@ -23,7 +24,7 @@ Resolve Problem
|
|
|
23
24
|
|
|
24
25
|
<expand name="getopt"
|
|
25
26
|
arg1="ase-code-resolve"
|
|
26
|
-
arg2="--auto|-a --dry|-d --quick|-Q --next|-n=(none|DONE|EDIT|GRILL|PREFLIGHT|IMPLEMENT)...">
|
|
27
|
+
arg2="--auto|-a --dry|-d --direct|-D --quick|-Q --next|-n=(none|DONE|EDIT|GRILL|PREFLIGHT|IMPLEMENT)...">
|
|
27
28
|
$ARGUMENTS
|
|
28
29
|
</expand>
|
|
29
30
|
|
|
@@ -45,9 +46,18 @@ to `true`, <getopt-option-dry/> to `true`, and <getopt-option-next/> to
|
|
|
45
46
|
Procedure
|
|
46
47
|
---------
|
|
47
48
|
|
|
49
|
+
<if condition="<getopt-option-direct/> is not equal to 'true'">
|
|
48
50
|
You *MUST* *NOT* call `Edit`, `Write`, `NotebookEdit`, or any
|
|
49
51
|
filesystem-modifying tool during this entire skill. The *only*
|
|
50
52
|
permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
53
|
+
</if>
|
|
54
|
+
<else>
|
|
55
|
+
The `--direct`/`-D` mode applies the resolution *in place*, so STEP 4
|
|
56
|
+
below *requires* `Edit` and `Write` to modify the affected artifacts.
|
|
57
|
+
Every modification *MUST* still stay restricted to the artifacts the
|
|
58
|
+
resolution actually demands, and you *MUST* *NOT* call
|
|
59
|
+
`ase_task_save(...)`, as no task plan is composed at all.
|
|
60
|
+
</else>
|
|
51
61
|
|
|
52
62
|
<flow>
|
|
53
63
|
|
|
@@ -120,14 +130,19 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
120
130
|
⧉ **ASE**: ⇌ problem: **<problem/>**
|
|
121
131
|
</template>
|
|
122
132
|
|
|
123
|
-
|
|
133
|
+
</step>
|
|
124
134
|
|
|
125
|
-
|
|
126
|
-
too unclear.
|
|
135
|
+
2. <step id="STEP 2: Investigate Code Base">
|
|
127
136
|
|
|
128
|
-
|
|
137
|
+
1. Check the existing source files for all code which is related to the
|
|
138
|
+
requested <problem/> resolution.
|
|
129
139
|
|
|
130
|
-
|
|
140
|
+
2. Check the architecture of the existing code base to understand the
|
|
141
|
+
overall structures and dynamics.
|
|
142
|
+
|
|
143
|
+
3. Investigate and *figure out details* related to this problem.
|
|
144
|
+
|
|
145
|
+
<if condition="<getopt-option-direct/> is not equal to 'true'">
|
|
131
146
|
Report those details with the following <template/>:
|
|
132
147
|
|
|
133
148
|
<template>
|
|
@@ -165,38 +180,47 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
165
180
|
run_in_background: false)`, reproducing its
|
|
166
181
|
returned fenced code block verbatim. Omit <optional-diagram/>
|
|
167
182
|
entirely for simple or purely local situations.
|
|
183
|
+
</if>
|
|
184
|
+
|
|
185
|
+
4. Do not output anything else in this STEP 2.
|
|
168
186
|
|
|
169
187
|
</step>
|
|
170
188
|
|
|
171
|
-
|
|
189
|
+
3. <step id="STEP 3: Internalize Problem Resolution Tenets">
|
|
172
190
|
|
|
173
|
-
1.
|
|
174
|
-
requested <problem/> resolution.
|
|
191
|
+
1. <task-kind>RESOLVING</task-kind>
|
|
175
192
|
|
|
176
|
-
2.
|
|
177
|
-
overall structures and dynamics.
|
|
193
|
+
2. <expand name="code-tenets" arg1="<task-kind/>"></expand>
|
|
178
194
|
|
|
179
|
-
3. Do not output anything in this STEP
|
|
195
|
+
3. Do not output anything in this STEP 3.
|
|
180
196
|
|
|
181
197
|
</step>
|
|
182
198
|
|
|
183
|
-
|
|
199
|
+
4. <if condition="<getopt-option-direct/> is equal to 'true'">
|
|
184
200
|
|
|
185
|
-
|
|
201
|
+
<step id="STEP 4: Direct Problem Resolution">
|
|
186
202
|
|
|
187
|
-
|
|
203
|
+
1. Directly resolve the <problem/> by modifying the affected
|
|
204
|
+
*artifacts* with a corresponding, complete *change set*,
|
|
205
|
+
based on your gathered knowledge about the code base and your
|
|
206
|
+
internalized problem resolution tenets. Also, if a CHANGELOG.md
|
|
207
|
+
file exists, make an appropriate entry there, too.
|
|
188
208
|
|
|
189
|
-
|
|
209
|
+
2. Do not output anything else in this STEP 4. Especially, do not
|
|
210
|
+
output a change summary or a unified diff of the changes.
|
|
190
211
|
|
|
191
212
|
</step>
|
|
192
213
|
|
|
193
|
-
|
|
214
|
+
</if>
|
|
215
|
+
<else>
|
|
216
|
+
|
|
217
|
+
<step id="STEP 4: Choose Problem Resolution Approaches">
|
|
194
218
|
|
|
195
219
|
<expand name="code-approaches" arg1="resolution" arg2="resolution"></expand>
|
|
196
220
|
|
|
197
221
|
</step>
|
|
198
222
|
|
|
199
|
-
|
|
223
|
+
<step id="STEP 5: Compose Problem Resolution Plan">
|
|
200
224
|
|
|
201
225
|
1. *Compose a plan* with code references, a precise description of the
|
|
202
226
|
problem, the chosen resolution approach, a preview of the *unified
|
|
@@ -248,5 +272,7 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
248
272
|
|
|
249
273
|
</step>
|
|
250
274
|
|
|
275
|
+
</else>
|
|
276
|
+
|
|
251
277
|
</flow>
|
|
252
278
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
[`--help`|`-h`]
|
|
10
10
|
[`--auto`|`-a`]
|
|
11
11
|
[`--dry`|`-d`]
|
|
12
|
+
[`--direct`|`-D`]
|
|
12
13
|
[`--quick`|`-Q`]
|
|
13
14
|
[`--next`|`-n` *option*[,...]]
|
|
14
15
|
[*task-id*:] *problem*
|
|
@@ -27,10 +28,11 @@ or `T1`) previously produced by `ase-code-analyze` or
|
|
|
27
28
|
`ase-arch-analyze` and persisted in the `ase` MCP key/value store
|
|
28
29
|
under `ase-issue-<id>`.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
plan via `ase_task_save` and then hands off to
|
|
32
|
-
`ase-task-preflight`, or `ase-task-implement`, as
|
|
33
|
-
`--next`.
|
|
31
|
+
By default the skill does *not* directly modify source files. It
|
|
32
|
+
persists the plan via `ase_task_save` and then hands off to
|
|
33
|
+
`ase-task-edit`, `ase-task-preflight`, or `ase-task-implement`, as
|
|
34
|
+
selected by `--next`. Only under `--direct` it skips the plan
|
|
35
|
+
entirely and applies the change set to the affected artifacts itself.
|
|
34
36
|
|
|
35
37
|
## OPTIONS
|
|
36
38
|
|
|
@@ -45,6 +47,15 @@ plan via `ase_task_save` and then hands off to `ase-task-edit`,
|
|
|
45
47
|
type-checker, or program execution) once the source files have
|
|
46
48
|
been modified.
|
|
47
49
|
|
|
50
|
+
`--direct`|`-D`:
|
|
51
|
+
Resolve the problem *immediately* and *in place*: skip the
|
|
52
|
+
resolution approaches, the interactive dialog, and the entire task
|
|
53
|
+
plan ceremony, and directly apply the complete change set to the
|
|
54
|
+
affected artifacts, including a corresponding entry in an existing
|
|
55
|
+
`CHANGELOG.md` file. In this mode `--auto`, `--dry`, `--quick`, and
|
|
56
|
+
`--next` have no effect, as neither approaches are proposed nor a
|
|
57
|
+
plan is composed.
|
|
58
|
+
|
|
48
59
|
`--quick`|`-Q`:
|
|
49
60
|
Shorthand alias for `-a -d -n IMPLEMENT,DELETE`: automatically pick
|
|
50
61
|
the recommended resolution approach, compose the plan *without* the
|
|
@@ -28,6 +28,15 @@ Query Foreign LLM for Chat
|
|
|
28
28
|
Query foreign LLM for: <query><getopt-arguments/></query>
|
|
29
29
|
</objective>
|
|
30
30
|
|
|
31
|
+
<if condition="<query/> is empty">
|
|
32
|
+
Only output the following <template/> and then immediately *STOP*
|
|
33
|
+
processing the entire current skill:
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
⧉ **ASE**: ✪ skill: **ase-meta-chat**, ▶ ERROR: expected a `<query>` argument
|
|
37
|
+
</template>
|
|
38
|
+
</if>
|
|
39
|
+
|
|
31
40
|
1. You *MUST* *NOT* output anything in this step.
|
|
32
41
|
Just call the underlying agent with the following tool:
|
|
33
42
|
|
|
@@ -78,7 +78,8 @@ preset-bootstrapping operation that stays a shell concern next to
|
|
|
78
78
|
`user`, `project`, `task:`*id*, and/or `session:`*id* terms, in any
|
|
79
79
|
order and at most one term per kind. The `user` term is always
|
|
80
80
|
implicitly added at the bottom of the chain, and `project` is
|
|
81
|
-
implicitly added whenever a project context exists
|
|
81
|
+
implicitly added whenever a project context exists, but never above
|
|
82
|
+
the strongest explicitly requested term. If the option is
|
|
82
83
|
omitted, the chain of the *current session* (`session:`*id*) is used,
|
|
83
84
|
so that reads see the full `user` -> `project` -> `session` cascade
|
|
84
85
|
and writes -- including those to the session-only keys `agent.task`
|
|
@@ -143,32 +143,10 @@ Procedure
|
|
|
143
143
|
</template>
|
|
144
144
|
|
|
145
145
|
- If <result/> is `EDIT`:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Set <args><args/> --next <getopt-option-next/></args>
|
|
149
|
-
</if>
|
|
150
|
-
Only output the following <template/> and then call the
|
|
151
|
-
tool `Skill(skill: "ase:ase-task-edit", args: "<args/>")`
|
|
152
|
-
to invoke the `ase:ase-task-edit` skill in order to *edit*
|
|
153
|
-
the updated plan. Immediately stop processing the current
|
|
154
|
-
skill once the `Skill` tool was used.
|
|
155
|
-
|
|
156
|
-
<template>
|
|
157
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to edit**
|
|
158
|
-
</template>
|
|
146
|
+
<expand name="task-next-handoff" arg1="ase-task-edit"
|
|
147
|
+
arg2="plan updated -- hand-off to edit"></expand>
|
|
159
148
|
|
|
160
149
|
- If <result/> is `IMPLEMENT`:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Set <args><args/> --next <getopt-option-next/></args>
|
|
164
|
-
</if>
|
|
165
|
-
Only output the following <template/> and then call the
|
|
166
|
-
tool `Skill(skill: "ase:ase-task-implement", args: "<args/>")`
|
|
167
|
-
to invoke the `ase:ase-task-implement` skill in order to
|
|
168
|
-
*implement* the updated plan. Immediately stop processing
|
|
169
|
-
the current skill once the `Skill` tool was used.
|
|
170
|
-
|
|
171
|
-
<template>
|
|
172
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to implementation**
|
|
173
|
-
</template>
|
|
150
|
+
<expand name="task-next-handoff" arg1="ase-task-implement"
|
|
151
|
+
arg2="plan updated -- hand-off to implementation"></expand>
|
|
174
152
|
|
|
@@ -143,47 +143,14 @@ Procedure
|
|
|
143
143
|
</template>
|
|
144
144
|
|
|
145
145
|
- If <result/> is `EDIT`:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Set <args><args/> --next <getopt-option-next/></args>
|
|
149
|
-
</if>
|
|
150
|
-
Only output the following <template/> and then call the
|
|
151
|
-
tool `Skill(skill: "ase:ase-task-edit", args: "<args/>")`
|
|
152
|
-
to invoke the `ase:ase-task-edit` skill in order to *edit*
|
|
153
|
-
the updated plan. Immediately stop processing the current
|
|
154
|
-
skill once the `Skill` tool was used.
|
|
155
|
-
|
|
156
|
-
<template>
|
|
157
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted -- hand-off to edit**
|
|
158
|
-
</template>
|
|
146
|
+
<expand name="task-next-handoff" arg1="ase-task-edit"
|
|
147
|
+
arg2="plan rebooted -- hand-off to edit"></expand>
|
|
159
148
|
|
|
160
149
|
- If <result/> is `IMPLEMENT`:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Set <args><args/> --next <getopt-option-next/></args>
|
|
164
|
-
</if>
|
|
165
|
-
Only output the following <template/> and then call the
|
|
166
|
-
tool `Skill(skill: "ase:ase-task-implement", args: "<args/>")`
|
|
167
|
-
to invoke the `ase:ase-task-implement` skill in order to
|
|
168
|
-
*implement* the updated plan. Immediately stop processing
|
|
169
|
-
the current skill once the `Skill` tool was used.
|
|
170
|
-
|
|
171
|
-
<template>
|
|
172
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted -- hand-off to implementation**
|
|
173
|
-
</template>
|
|
150
|
+
<expand name="task-next-handoff" arg1="ase-task-implement"
|
|
151
|
+
arg2="plan rebooted -- hand-off to implementation"></expand>
|
|
174
152
|
|
|
175
153
|
- If <result/> is `PREFLIGHT`:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Set <args><args/> --next <getopt-option-next/></args>
|
|
179
|
-
</if>
|
|
180
|
-
Only output the following <template/> and then call the
|
|
181
|
-
tool `Skill(skill: "ase:ase-task-preflight", args: "<args/>")`
|
|
182
|
-
to invoke the `ase:ase-task-preflight` skill in order to
|
|
183
|
-
*preflight* the updated plan. Immediately stop processing
|
|
184
|
-
the current skill once the `Skill` tool was used.
|
|
185
|
-
|
|
186
|
-
<template>
|
|
187
|
-
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted -- hand-off to pre-flight**
|
|
188
|
-
</template>
|
|
154
|
+
<expand name="task-next-handoff" arg1="ase-task-preflight"
|
|
155
|
+
arg2="plan rebooted -- hand-off to pre-flight"></expand>
|
|
189
156
|
|