@rse/ase 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dst/ase-getopt.js +71 -5
- package/dst/ase-hook.js +6 -21
- package/dst/ase-markdown.js +32 -11
- package/dst/ase-mcp.js +22 -8
- package/dst/ase-notify.js +32 -0
- package/dst/ase-service.js +5 -2
- package/dst/ase-setup.js +45 -131
- package/dst/ase-skills.js +17 -13
- package/dst/ase-statusline.js +8 -12
- package/dst/ase-task.js +32 -23
- package/package.json +3 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/agents/ase-docs-proofread.md +2 -2
- package/plugin/meta/ase-constitution.md +7 -0
- package/plugin/meta/ase-control.md +24 -3
- package/plugin/meta/ase-dialog.md +105 -7
- package/plugin/meta/ase-format-task.md +2 -2
- package/plugin/meta/ase-getopt.md +31 -22
- package/plugin/meta/ase-skill.md +87 -12
- package/plugin/package.json +2 -2
- package/plugin/skills/ase-arch-analyze/SKILL.md +88 -89
- package/plugin/skills/ase-arch-analyze/help.md +2 -2
- package/plugin/skills/ase-arch-discover/SKILL.md +56 -34
- package/plugin/skills/ase-arch-discover/help.md +1 -1
- package/plugin/skills/ase-code-analyze/SKILL.md +6 -5
- package/plugin/skills/ase-code-analyze/help.md +2 -2
- package/plugin/skills/ase-code-craft/SKILL.md +83 -71
- package/plugin/skills/ase-code-craft/help.md +2 -2
- package/plugin/skills/ase-code-explain/SKILL.md +1 -1
- package/plugin/skills/ase-code-explain/help.md +1 -1
- package/plugin/skills/ase-code-insight/SKILL.md +1 -1
- package/plugin/skills/ase-code-insight/help.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +35 -18
- package/plugin/skills/ase-code-lint/help.md +2 -2
- package/plugin/skills/ase-code-refactor/SKILL.md +81 -70
- package/plugin/skills/ase-code-refactor/help.md +2 -2
- package/plugin/skills/ase-code-resolve/SKILL.md +83 -70
- package/plugin/skills/ase-code-resolve/help.md +3 -3
- package/plugin/skills/ase-docs-distill/SKILL.md +1 -1
- package/plugin/skills/ase-docs-distill/help.md +4 -4
- package/plugin/skills/ase-docs-proofread/SKILL.md +36 -19
- package/plugin/skills/ase-docs-proofread/help.md +1 -1
- package/plugin/skills/ase-meta-brainstorm/SKILL.md +29 -8
- package/plugin/skills/ase-meta-brainstorm/help.md +7 -11
- package/plugin/skills/ase-meta-changelog/help.md +1 -1
- package/plugin/skills/ase-meta-chat/help.md +1 -1
- package/plugin/skills/ase-meta-commit/help.md +1 -1
- package/plugin/skills/ase-meta-diaboli/help.md +2 -2
- package/plugin/skills/ase-meta-diff/SKILL.md +6 -5
- package/plugin/skills/ase-meta-diff/help.md +11 -12
- package/plugin/skills/ase-meta-evaluate/SKILL.md +10 -9
- package/plugin/skills/ase-meta-evaluate/help.md +2 -2
- package/plugin/skills/ase-meta-persona/help.md +1 -1
- package/plugin/skills/ase-meta-quorum/SKILL.md +15 -5
- package/plugin/skills/ase-meta-quorum/help.md +1 -1
- package/plugin/skills/ase-meta-review/SKILL.md +3 -4
- package/plugin/skills/ase-meta-review/help.md +5 -5
- package/plugin/skills/ase-meta-search/SKILL.md +9 -8
- package/plugin/skills/ase-meta-search/help.md +1 -1
- package/plugin/skills/ase-meta-steelman/SKILL.md +1 -1
- package/plugin/skills/ase-meta-steelman/help.md +2 -2
- package/plugin/skills/ase-meta-why/SKILL.md +16 -10
- package/plugin/skills/ase-meta-why/help.md +1 -1
- package/plugin/skills/ase-task-condense/SKILL.md +36 -19
- package/plugin/skills/ase-task-condense/help.md +3 -3
- package/plugin/skills/ase-task-delete/SKILL.md +6 -3
- package/plugin/skills/ase-task-delete/help.md +2 -2
- package/plugin/skills/ase-task-edit/SKILL.md +61 -36
- package/plugin/skills/ase-task-edit/help.md +4 -4
- package/plugin/skills/ase-task-grill/SKILL.md +57 -26
- package/plugin/skills/ase-task-grill/help.md +3 -3
- package/plugin/skills/ase-task-id/SKILL.md +11 -2
- package/plugin/skills/ase-task-id/help.md +2 -2
- package/plugin/skills/ase-task-implement/SKILL.md +40 -17
- package/plugin/skills/ase-task-implement/help.md +2 -2
- package/plugin/skills/ase-task-list/SKILL.md +1 -1
- package/plugin/skills/ase-task-list/help.md +2 -2
- package/plugin/skills/ase-task-preflight/SKILL.md +44 -22
- package/plugin/skills/ase-task-preflight/help.md +3 -3
- package/plugin/skills/ase-task-reboot/SKILL.md +31 -20
- package/plugin/skills/ase-task-reboot/help.md +2 -2
- package/plugin/skills/ase-task-rename/SKILL.md +5 -3
- package/plugin/skills/ase-task-rename/help.md +2 -2
- package/plugin/skills/ase-task-view/help.md +26 -7
package/dst/ase-skills.js
CHANGED
|
@@ -242,27 +242,31 @@ export class Skills {
|
|
|
242
242
|
return [];
|
|
243
243
|
}
|
|
244
244
|
/* compute composite rank score from weighted metrics:
|
|
245
|
-
downloads x
|
|
246
|
-
stars x
|
|
245
|
+
(downloads + 1) x
|
|
246
|
+
(stars + 1) x
|
|
247
247
|
([lifespan =] (updated - created)) x
|
|
248
248
|
([recentness =] exp(-(now - updated) / halfLife))
|
|
249
|
-
|
|
250
|
-
a
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
Numeric count metrics are shifted by `+1` so that a genuine `0`
|
|
250
|
+
(e.g. a real package with zero downloads or stars) contributes a
|
|
251
|
+
neutral `1` instead of collapsing the entire product to zero, while
|
|
252
|
+
still ordering `0 < 1 < 2 ...`. The `"N.A."` sentinel (a metric that
|
|
253
|
+
is structurally unavailable, e.g. Maven Central exposes no
|
|
254
|
+
per-artifact download counts) is likewise treated as neutral `1`, so
|
|
255
|
+
such stacks can still be ranked by the remaining metrics. */
|
|
253
256
|
static computeRank(downloads, stars, created, updated) {
|
|
254
|
-
const d = typeof downloads === "number" ? downloads : 1;
|
|
255
|
-
const s = typeof stars === "number" ? stars : 1;
|
|
257
|
+
const d = typeof downloads === "number" ? downloads + 1 : 1;
|
|
258
|
+
const s = typeof stars === "number" ? stars + 1 : 1;
|
|
256
259
|
const cMs = created !== "" ? Date.parse(created) : NaN;
|
|
257
260
|
const uMs = updated !== "" ? Date.parse(updated) : NaN;
|
|
258
|
-
if (Number.isNaN(cMs) || Number.isNaN(uMs))
|
|
259
|
-
return 0;
|
|
260
261
|
const now = Date.now();
|
|
261
262
|
const msPerDay = 1000 * 60 * 60 * 24;
|
|
262
263
|
const halfLife = 365 / 2;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
/* lifespan requires both timestamps; recentness requires the
|
|
265
|
+
updated timestamp -- any unavailable date-derived factor is
|
|
266
|
+
treated as neutral `1` so the entry can still be ranked by the
|
|
267
|
+
remaining metrics, instead of collapsing the product to zero */
|
|
268
|
+
const lifespan = (!Number.isNaN(cMs) && !Number.isNaN(uMs)) ? Math.max(0, uMs - cMs) : 1;
|
|
269
|
+
const recentness = !Number.isNaN(uMs) ? Math.exp(-Math.max(0, (now - uMs) / msPerDay) / halfLife) : 1;
|
|
266
270
|
return d * s * lifespan * recentness;
|
|
267
271
|
}
|
|
268
272
|
/* compute the per-alternative product-sum (rating) row from a
|
package/dst/ase-statusline.js
CHANGED
|
@@ -245,12 +245,14 @@ export default class StatuslineCommand {
|
|
|
245
245
|
out += ansi;
|
|
246
246
|
col += raw.length;
|
|
247
247
|
};
|
|
248
|
-
/* active <color> span state: when non-null,
|
|
249
|
-
|
|
248
|
+
/* active <color> span state: when non-null, each emitted chunk is colored
|
|
249
|
+
individually and passed straight through appendOutput, so column
|
|
250
|
+
accounting and line-wrapping continue to work per-chunk inside the span
|
|
251
|
+
instead of treating the whole colored run as one atomic chunk */
|
|
250
252
|
let span = null;
|
|
251
253
|
const emit = (chunk) => {
|
|
252
|
-
if (span !== null)
|
|
253
|
-
span.
|
|
254
|
+
if (span !== null && span.color !== "default")
|
|
255
|
+
appendOutput((c[span.color])(chunk));
|
|
254
256
|
else
|
|
255
257
|
appendOutput(chunk);
|
|
256
258
|
};
|
|
@@ -446,13 +448,7 @@ export default class StatuslineCommand {
|
|
|
446
448
|
};
|
|
447
449
|
/* walk each template line and render */
|
|
448
450
|
const closeSpan = () => {
|
|
449
|
-
|
|
450
|
-
const wrapped = span.color === "default" ?
|
|
451
|
-
span.buf :
|
|
452
|
-
(c[span.color])(span.buf);
|
|
453
|
-
span = null;
|
|
454
|
-
appendOutput(wrapped);
|
|
455
|
-
}
|
|
451
|
+
span = null;
|
|
456
452
|
};
|
|
457
453
|
for (const line of tmpl) {
|
|
458
454
|
let i = 0;
|
|
@@ -465,7 +461,7 @@ export default class StatuslineCommand {
|
|
|
465
461
|
if (m[1] === "/")
|
|
466
462
|
closeSpan();
|
|
467
463
|
else if (span === null)
|
|
468
|
-
span = { color: m[2]
|
|
464
|
+
span = { color: m[2] };
|
|
469
465
|
i += m[0].length;
|
|
470
466
|
continue;
|
|
471
467
|
}
|
package/dst/ase-task.js
CHANGED
|
@@ -20,8 +20,8 @@ export class Task {
|
|
|
20
20
|
static validateId(id) {
|
|
21
21
|
if (typeof id !== "string" || id.length === 0)
|
|
22
22
|
throw new Error("task: id must be a non-empty string");
|
|
23
|
-
if (!/^[A-Za-z0-
|
|
24
|
-
throw new Error("task: id must match [A-Za-z0-
|
|
23
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id))
|
|
24
|
+
throw new Error("task: id must match [A-Za-z0-9_-]+");
|
|
25
25
|
}
|
|
26
26
|
/* determine the project root (Git top-level if inside a Git
|
|
27
27
|
working tree, otherwise the current working directory) */
|
|
@@ -59,12 +59,22 @@ export class Task {
|
|
|
59
59
|
static baseDir(log) {
|
|
60
60
|
return path.join(Task.projectRoot(), Task.spec(log).basedir);
|
|
61
61
|
}
|
|
62
|
+
/* ensure a task id's "TASK-<id>.md" filename satisfies
|
|
63
|
+
the configured "files" miniglob */
|
|
64
|
+
static enforceFiles(log, id) {
|
|
65
|
+
const { files } = Task.spec(log);
|
|
66
|
+
const filename = `TASK-${id}.md`;
|
|
67
|
+
if (!picomatch(files, { dot: true })(filename))
|
|
68
|
+
throw new Error(`task: id "${id}" yields filename "${filename}" ` +
|
|
69
|
+
`which does not match the configured "files" glob "${files}"`);
|
|
70
|
+
}
|
|
62
71
|
/* resolve the on-disk path for a given task id; as a side effect,
|
|
63
72
|
eagerly migrate any legacy <basedir>/<id>/plan.md files to the
|
|
64
73
|
current <basedir>/TASK-<id>.md layout on first access (guarded by
|
|
65
74
|
a cheap check, so it is a no-op once the store is migrated) */
|
|
66
75
|
static path(log, id) {
|
|
67
76
|
Task.validateId(id);
|
|
77
|
+
Task.enforceFiles(log, id);
|
|
68
78
|
if (Task.needsMigration(log))
|
|
69
79
|
Task.migrateAll(log);
|
|
70
80
|
return path.join(Task.baseDir(log), `TASK-${id}.md`);
|
|
@@ -76,7 +86,7 @@ export class Task {
|
|
|
76
86
|
if (!fs.existsSync(dir))
|
|
77
87
|
return false;
|
|
78
88
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
79
|
-
if (!entry.isDirectory() || !/^[A-Za-z0-
|
|
89
|
+
if (!entry.isDirectory() || !/^[A-Za-z0-9_-]+$/.test(entry.name))
|
|
80
90
|
continue;
|
|
81
91
|
if (fs.existsSync(path.join(dir, entry.name, "plan.md")))
|
|
82
92
|
return true;
|
|
@@ -93,7 +103,7 @@ export class Task {
|
|
|
93
103
|
return [];
|
|
94
104
|
const migrated = [];
|
|
95
105
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
96
|
-
if (!entry.isDirectory() || !/^[A-Za-z0-
|
|
106
|
+
if (!entry.isDirectory() || !/^[A-Za-z0-9_-]+$/.test(entry.name))
|
|
97
107
|
continue;
|
|
98
108
|
const id = entry.name;
|
|
99
109
|
const oldFile = path.join(dir, id, "plan.md");
|
|
@@ -138,7 +148,7 @@ export class Task {
|
|
|
138
148
|
}
|
|
139
149
|
/* rename a task by moving its <project>/<basedir>/TASK-<oldId>.md file
|
|
140
150
|
to <project>/<basedir>/TASK-<newId>.md; the embedded
|
|
141
|
-
"#
|
|
151
|
+
"# TASK <id>:" heading inside the plan content is rewritten to
|
|
142
152
|
the new id; returns true on success, false if the source task does
|
|
143
153
|
not exist; throws if the target id already exists */
|
|
144
154
|
static rename(log, oldId, newId) {
|
|
@@ -149,7 +159,7 @@ export class Task {
|
|
|
149
159
|
if (fs.existsSync(newFile))
|
|
150
160
|
throw new Error(`task: target id "${newId}" already exists`);
|
|
151
161
|
const text = fs.readFileSync(oldFile, "utf8");
|
|
152
|
-
const updated = text.replace(/(^#\s+
|
|
162
|
+
const updated = text.replace(/(^#\s+TASK\s+)[A-Za-z0-9_-]+(\s*:)/m, `$1${newId}$2`);
|
|
153
163
|
fs.mkdirSync(path.dirname(newFile), { recursive: true });
|
|
154
164
|
fs.writeFileSync(newFile, updated, "utf8");
|
|
155
165
|
fs.rmSync(oldFile, { force: true });
|
|
@@ -168,7 +178,7 @@ export class Task {
|
|
|
168
178
|
const isMatch = picomatch(files, { dot: true });
|
|
169
179
|
const out = [];
|
|
170
180
|
for (const entry of fs.readdirSync(dir)) {
|
|
171
|
-
const m = /^TASK-([A-Za-z0-
|
|
181
|
+
const m = /^TASK-([A-Za-z0-9_-]+)\.md$/.exec(entry);
|
|
172
182
|
if (m === null || !isMatch(entry))
|
|
173
183
|
continue;
|
|
174
184
|
const file = path.join(dir, entry);
|
|
@@ -194,7 +204,7 @@ export class Task {
|
|
|
194
204
|
const cutoff = Date.now() - maxAgeMs;
|
|
195
205
|
const removed = [];
|
|
196
206
|
for (const entry of fs.readdirSync(dir)) {
|
|
197
|
-
const m = /^TASK-([A-Za-z0-
|
|
207
|
+
const m = /^TASK-([A-Za-z0-9_-]+)\.md$/.exec(entry);
|
|
198
208
|
if (m === null || !isMatch(entry))
|
|
199
209
|
continue;
|
|
200
210
|
const file = path.join(dir, entry);
|
|
@@ -417,7 +427,7 @@ export class TaskMCP {
|
|
|
417
427
|
"Returns the task as `text`; returns an empty string if no task exists for the `id`.",
|
|
418
428
|
inputSchema: {
|
|
419
429
|
id: z.string()
|
|
420
|
-
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
430
|
+
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
|
|
421
431
|
}
|
|
422
432
|
}, async (args) => {
|
|
423
433
|
try {
|
|
@@ -442,16 +452,15 @@ export class TaskMCP {
|
|
|
442
452
|
"Overwrites any existing task for the same `id`.",
|
|
443
453
|
inputSchema: {
|
|
444
454
|
id: z.string()
|
|
445
|
-
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')"),
|
|
455
|
+
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')"),
|
|
446
456
|
text: z.string()
|
|
447
457
|
.describe("text content of the task")
|
|
448
458
|
}
|
|
449
459
|
}, async (args) => {
|
|
450
460
|
try {
|
|
451
|
-
|
|
452
|
-
Task.save(this.log, args.id, text);
|
|
461
|
+
Task.save(this.log, args.id, args.text);
|
|
453
462
|
return {
|
|
454
|
-
content: [{ type: "text", text: `
|
|
463
|
+
content: [{ type: "text", text: `OK: saved task "${args.id}"` }]
|
|
455
464
|
};
|
|
456
465
|
}
|
|
457
466
|
catch (err) {
|
|
@@ -469,14 +478,14 @@ export class TaskMCP {
|
|
|
469
478
|
"Returns a status `text` indicating whether a task existed and was removed.",
|
|
470
479
|
inputSchema: {
|
|
471
480
|
id: z.string()
|
|
472
|
-
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
481
|
+
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
|
|
473
482
|
}
|
|
474
483
|
}, async (args) => {
|
|
475
484
|
try {
|
|
476
485
|
const removed = Task.delete(this.log, args.id);
|
|
477
486
|
const msg = removed ?
|
|
478
|
-
`
|
|
479
|
-
`
|
|
487
|
+
`OK: removed task "${args.id}"` :
|
|
488
|
+
`WARNING: no task "${args.id}" to remove`;
|
|
480
489
|
return {
|
|
481
490
|
content: [{ type: "text", text: msg }]
|
|
482
491
|
};
|
|
@@ -497,16 +506,16 @@ export class TaskMCP {
|
|
|
497
506
|
"Fails with an error if the target id already exists.",
|
|
498
507
|
inputSchema: {
|
|
499
508
|
old: z.string()
|
|
500
|
-
.describe("old task identifier (allowed characters: A-Z, a-z, 0-9, '-')"),
|
|
509
|
+
.describe("old task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')"),
|
|
501
510
|
new: z.string()
|
|
502
|
-
.describe("new task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
511
|
+
.describe("new task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
|
|
503
512
|
}
|
|
504
513
|
}, async (args) => {
|
|
505
514
|
try {
|
|
506
515
|
const renamed = Task.rename(this.log, args.old, args.new);
|
|
507
516
|
const msg = renamed ?
|
|
508
|
-
`
|
|
509
|
-
`
|
|
517
|
+
`OK: renamed task "${args.old}" to "${args.new}"` :
|
|
518
|
+
`WARNING: no task "${args.old}" to rename`;
|
|
510
519
|
return {
|
|
511
520
|
content: [{ type: "text", text: msg }]
|
|
512
521
|
};
|
|
@@ -527,16 +536,16 @@ export class TaskMCP {
|
|
|
527
536
|
"otherwise it returns the current task `id` of the `session`.",
|
|
528
537
|
inputSchema: {
|
|
529
538
|
id: z.string().optional()
|
|
530
|
-
.describe("task identifier to set (allowed characters: A-Z, a-z, 0-9, '-'); " +
|
|
539
|
+
.describe("task identifier to set (allowed characters: A-Z, a-z, 0-9, '_', '-'); " +
|
|
531
540
|
"if omitted, the current task id is returned"),
|
|
532
541
|
session: z.string()
|
|
533
|
-
.describe("session identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
542
|
+
.describe("session identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
|
|
534
543
|
}
|
|
535
544
|
}, async (args) => {
|
|
536
545
|
try {
|
|
537
546
|
if (args.id !== undefined) {
|
|
538
547
|
Task.setId(this.log, args.session, args.id);
|
|
539
|
-
const msg = `
|
|
548
|
+
const msg = `OK: set agent.task to "${args.id}" ` +
|
|
540
549
|
`for session "${args.session}"`;
|
|
541
550
|
return {
|
|
542
551
|
content: [{ type: "text", text: msg }]
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "http://github.com/rse/ase",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "http://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.9",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"nodemon": "3.1.14",
|
|
31
31
|
"shx": "0.4.0",
|
|
32
32
|
|
|
33
|
-
"@types/node": "25.9.
|
|
33
|
+
"@types/node": "25.9.3",
|
|
34
34
|
"@types/luxon": "3.7.1",
|
|
35
35
|
"@types/which": "3.0.4",
|
|
36
36
|
"@types/update-notifier": "6.0.8",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"commander": "15.0.0",
|
|
45
|
-
"@dotenvx/dotenvx": "1.71.
|
|
45
|
+
"@dotenvx/dotenvx": "1.71.3",
|
|
46
46
|
"yaml": "2.9.0",
|
|
47
47
|
"valibot": "1.4.1",
|
|
48
48
|
"execa": "9.6.1",
|
|
@@ -62,9 +62,9 @@ Workflow
|
|
|
62
62
|
6. Set <context-after/> to exactly *up to two* lines of
|
|
63
63
|
*unchanged* text content which occurs in the document
|
|
64
64
|
directly *after* <old-text/>, i.e., the lines (<line/>
|
|
65
|
-
+ <n/>
|
|
65
|
+
+ <n/>) and (<line/> + <n/> + 1), where <n/> is the
|
|
66
66
|
number of lines in <old-text/>. Reduce to just one line
|
|
67
|
-
(<line/> + <n/>
|
|
67
|
+
(<line/> + <n/>) if <old-text/> is the second-last
|
|
68
68
|
line of the document. Set <context-after/> to empty if
|
|
69
69
|
<old-text/> is the last line in the document.
|
|
70
70
|
|
|
@@ -16,6 +16,13 @@ you *MUST* once and immediately output the following <template/> now:
|
|
|
16
16
|
⧉ **ASE**: ☯ persona: **<ase-persona-style/>**
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
|
+
In case your harness instructions indicate that the user is shown
|
|
20
|
+
only the *final* text message of each turn (e.g. *Claude Code* with
|
|
21
|
+
"focus mode" enabled), you *MUST* ensure this <template/> output (and
|
|
22
|
+
*every* <template/> output requested by ASE skills) lands in a final
|
|
23
|
+
text message (after the last tool call of a turn) instead of between
|
|
24
|
+
tool calls -- repeat it there if necessary.
|
|
25
|
+
|
|
19
26
|
Prohibitions
|
|
20
27
|
------------
|
|
21
28
|
|
|
@@ -14,7 +14,7 @@ Control Flow Constructs
|
|
|
14
14
|
Do not output anything.
|
|
15
15
|
|
|
16
16
|
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
17
|
-
<expand name="<define-name/>" [arg1="<expand-arg1/>" [arg2="<expand-arg2/>" [...]]]
|
|
17
|
+
<expand name="<define-name/>" [arg1="<expand-arg1/>" [arg2="<expand-arg2/>" [...]]]>[<expand-content/>]</expand>:
|
|
18
18
|
|
|
19
19
|
This specifies the *expansion* of previous <define/>. This
|
|
20
20
|
construct is expanded to the <define-body/> of <define/> with
|
|
@@ -48,14 +48,35 @@ Control Flow Constructs
|
|
|
48
48
|
|
|
49
49
|
This specifies a simple condition which is expanded to <if-body/>
|
|
50
50
|
if <if-condition/> is met, or to empty string if <if-condition/> is
|
|
51
|
-
*not* met.
|
|
51
|
+
*not* met. It can be optionally followed by one or more <elseif/>
|
|
52
|
+
constructs and/or one final <else/> construct. Do not output anything else.
|
|
53
|
+
|
|
54
|
+
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
55
|
+
<elseif condition="<elseif-condition/>"><elseif-body/></elseif>:
|
|
56
|
+
|
|
57
|
+
This specifies an *alternative condition* and has to directly
|
|
58
|
+
follow an <if/> or another <elseif/> construct. It is expanded
|
|
59
|
+
to <elseif-body/> if the conditions of all preceding <if/> and
|
|
60
|
+
<elseif/> constructs of the chain were *not* met and its own
|
|
61
|
+
<elseif-condition/> is met, or to the empty string otherwise.
|
|
62
|
+
Do not output anything else.
|
|
63
|
+
|
|
64
|
+
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
65
|
+
<else><else-body/></else>:
|
|
66
|
+
|
|
67
|
+
This specifies the *fallback alternative* and has to directly
|
|
68
|
+
follow an <if/> or <elseif/> construct. It is expanded to
|
|
69
|
+
<else-body/> if the conditions of all preceding <if/> and
|
|
70
|
+
<elseif/> constructs of the chain were *not* met, or to the empty
|
|
71
|
+
string otherwise. Do not output anything else.
|
|
52
72
|
|
|
53
73
|
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
54
74
|
<while condition="<while-condition/>"><while-body/></while>:
|
|
55
75
|
|
|
56
76
|
This specifies a <while-body/> which is *repeated* as long as
|
|
57
77
|
<while-condition/> is met. This construct is expanded to the
|
|
58
|
-
repetition of <while-body/>.
|
|
78
|
+
repetition of <while-body/>. A <break/> in <while-body/> can stop
|
|
79
|
+
the repetition early. Do not output anything else.
|
|
59
80
|
|
|
60
81
|
- *IMPORTANT*: You *MUST* honor the following control flow construct:
|
|
61
82
|
<for items="<for-item/> [...]"><for-body/></for>:
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
2
|
User Dialog
|
|
4
3
|
===========
|
|
5
4
|
|
|
@@ -15,13 +14,16 @@ User Dialog
|
|
|
15
14
|
Let the *user interactively choose* an answer.
|
|
16
15
|
|
|
17
16
|
1. Take the following question specification:
|
|
17
|
+
|
|
18
18
|
<spec>
|
|
19
19
|
<content/>
|
|
20
20
|
</spec>
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
The first line of <spec/> (separated by newlines) is of the format:
|
|
23
|
+
`<question-label/>: <question-description/>`
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
The second and following lines of <spec/> (separated by newlines) are of the format:
|
|
26
|
+
`<label/>: <description/>`
|
|
25
27
|
|
|
26
28
|
The first line provides the question label and the question
|
|
27
29
|
description. The second and following lines each provide an
|
|
@@ -49,8 +51,8 @@ Let the *user interactively choose* an answer.
|
|
|
49
51
|
|
|
50
52
|
If <n/> is less than 2:
|
|
51
53
|
Set <result>ERROR: user-dialog requires 2-4 answer lines, got <n/></result>
|
|
52
|
-
and *SKIP* the following step 2 (do not call `AskUserQuestion`)
|
|
53
|
-
and continue with step 3 dispatch.
|
|
54
|
+
and *SKIP* the following step 2.2 (do not call `AskUserQuestion`)
|
|
55
|
+
and continue with step 2.3 dispatch.
|
|
54
56
|
|
|
55
57
|
2. Call the `AskUserQuestion` tool of the agent harness with:
|
|
56
58
|
|
|
@@ -103,8 +105,8 @@ Let the *user interactively choose* an answer.
|
|
|
103
105
|
|
|
104
106
|
If <n/> is less than 2:
|
|
105
107
|
Set <result>ERROR: user-dialog requires 2-4 answer lines, got <n/></result>
|
|
106
|
-
and *SKIP* the following step 2 (do not call `ask_user`)
|
|
107
|
-
and continue with step 3 dispatch.
|
|
108
|
+
and *SKIP* the following step 2.2 (do not call `ask_user`)
|
|
109
|
+
and continue with step 2.3 dispatch.
|
|
108
110
|
|
|
109
111
|
2. Call the `ask_user` tool of the agent harness with:
|
|
110
112
|
|
|
@@ -137,3 +139,99 @@ Let the *user interactively choose* an answer.
|
|
|
137
139
|
|
|
138
140
|
</define>
|
|
139
141
|
|
|
142
|
+
<define name="custom-dialog">
|
|
143
|
+
Let the *user interactively choose* an answer.
|
|
144
|
+
|
|
145
|
+
1. Take the following question specification:
|
|
146
|
+
|
|
147
|
+
<spec>
|
|
148
|
+
<content/>
|
|
149
|
+
</spec>
|
|
150
|
+
|
|
151
|
+
The first line of <spec/> (separated by newlines) is of the format:
|
|
152
|
+
`<question-label/>: <question-description/>`
|
|
153
|
+
|
|
154
|
+
The second and following lines of <spec/> (separated by newlines) are of the format:
|
|
155
|
+
`<label/>: <description/>`
|
|
156
|
+
|
|
157
|
+
The first line provides the question label and the question
|
|
158
|
+
description. The second and following lines each provide an
|
|
159
|
+
answer label and an answer description.
|
|
160
|
+
|
|
161
|
+
Do not output anything in this step!
|
|
162
|
+
|
|
163
|
+
2. Dispatch according to the agent tool:
|
|
164
|
+
|
|
165
|
+
1. You *MUST* not output anything in this step.
|
|
166
|
+
|
|
167
|
+
Set <text></text> (set to empty).
|
|
168
|
+
Set <keys></keys> (set to empty).
|
|
169
|
+
Set <n>1</n> (set entry count to one).
|
|
170
|
+
Set <width/> to the maximum length plus 3 of all <label/> strings in <spec/>.
|
|
171
|
+
|
|
172
|
+
<for items="2 3 4 5 6 7 8 9">
|
|
173
|
+
Take from <spec/> the line number <item/>.
|
|
174
|
+
If this line does not exist, <break/>.
|
|
175
|
+
If this line exists, parse it according to the format `<label/>: <description/>`.
|
|
176
|
+
Set <label-key/> to <ase-tpl-key digit="<n/>"/>.
|
|
177
|
+
Set <label-text/> to `<ase-tpl-pad width="<width/>" text="<label/>:"/>`.
|
|
178
|
+
Append an entry to <text/>:
|
|
179
|
+
|
|
180
|
+
<text>
|
|
181
|
+
<text/>
|
|
182
|
+
<ase-tpl-boxline><label-key/> ▶ **<label-text/>** <description/></ase-tpl-boxline>
|
|
183
|
+
</text>
|
|
184
|
+
|
|
185
|
+
Set <n/> to <n/> + 1 (increment entry count).
|
|
186
|
+
<if condition="<keys/> is empty">
|
|
187
|
+
Set <keys><label-key/></keys>
|
|
188
|
+
</if>
|
|
189
|
+
<else>
|
|
190
|
+
Set <keys><keys/>/<label-key/></keys>
|
|
191
|
+
</else>
|
|
192
|
+
</for>
|
|
193
|
+
|
|
194
|
+
Set:
|
|
195
|
+
|
|
196
|
+
<text>
|
|
197
|
+
<ase-tpl-boxed title="QUESTION" subtitle="<question-label/>">
|
|
198
|
+
|
|
199
|
+
<ase-tpl-boxline>**<question-description/>**</ase-tpl-boxline>
|
|
200
|
+
|
|
201
|
+
<text/>
|
|
202
|
+
|
|
203
|
+
Please choose *one* option by typing <keys/>/**CANCEL** or free-text instruction.
|
|
204
|
+
|
|
205
|
+
</ase-tpl-boxed>
|
|
206
|
+
</text>
|
|
207
|
+
|
|
208
|
+
If <n/> is less than 3:
|
|
209
|
+
Set <result>ERROR: user-dialog requires 2-8 answer lines, got less</result>
|
|
210
|
+
and *SKIP* the following step 2 and continue with step 3 dispatch.
|
|
211
|
+
|
|
212
|
+
2. Output the following <template/>, end the current turn, wait for the
|
|
213
|
+
user input, store the user input in <result/> and then continue with step 3:
|
|
214
|
+
|
|
215
|
+
<template>
|
|
216
|
+
<text/>
|
|
217
|
+
</template>
|
|
218
|
+
|
|
219
|
+
3. Check the result and dispatch accordingly:
|
|
220
|
+
|
|
221
|
+
- If <result/> indicates that the user doesn't want to proceed,
|
|
222
|
+
or the user declined to answer the question, or that the dialog
|
|
223
|
+
was cancelled, rejected or skipped, set <result>CANCEL</result>.
|
|
224
|
+
|
|
225
|
+
- Otherwise, determine the selected <label/>
|
|
226
|
+
by mapping the <result/> (usually containing one of the
|
|
227
|
+
"key" or "label" strings) to one of the answer labels. Set
|
|
228
|
+
<result><label/></result>.
|
|
229
|
+
|
|
230
|
+
If <result/> is then *NOT* one of the "label" values from
|
|
231
|
+
<spec/>, set <result>OTHER: <result/></result> (prefix
|
|
232
|
+
result with "OTHER").
|
|
233
|
+
|
|
234
|
+
Do not output anything in this step!
|
|
235
|
+
|
|
236
|
+
</define>
|
|
237
|
+
|
|
@@ -8,30 +8,39 @@ Do not output anything in the following steps. The entire purpose is to
|
|
|
8
8
|
set placeholders into the context as a side-effect.
|
|
9
9
|
|
|
10
10
|
1. **Determine Parameters**:
|
|
11
|
-
Set <getopt-skill><arg1/></getopt-skill
|
|
12
|
-
Set <getopt-spec>--help|-h <arg2/></getopt-spec
|
|
13
|
-
Set <getopt-args><content/></getopt-args
|
|
11
|
+
Set <getopt-skill><arg1/></getopt-skill>
|
|
12
|
+
Set <getopt-spec>--help|-h <arg2/></getopt-spec>
|
|
13
|
+
Set <getopt-args><content/></getopt-args>
|
|
14
14
|
|
|
15
15
|
2. **Short-Circuit Processing**:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
You *MUST* decide here, via the following *mandatory* <if/> control
|
|
17
|
+
construct, whether the options are parsed *locally* (no MCP call) or
|
|
18
|
+
*remotely* (via the MCP call in steps 3-6). This is a *hard branch*,
|
|
19
|
+
not an optional optimization: when the <if/> branch is taken, steps
|
|
20
|
+
3-6 are *structurally unreachable* and you *MUST NOT* call the
|
|
21
|
+
`ase_getopt` MCP tool under any circumstances.
|
|
22
|
+
|
|
23
|
+
<if condition="<getopt-args/> does *NOT* match the regexp `(^|\s)-` (i.e. it does not start with an option)">
|
|
24
|
+
Parse the options *locally*, without any MCP call:
|
|
25
|
+
|
|
26
|
+
For each option token in <getopt-spec/> of the form
|
|
27
|
+
`--<long/>[|-<short/>][=<default/>|=(<c1/>|<c2/>|...)[...]]`, set
|
|
28
|
+
<getopt-option-<long/>/> to <default/> (for `=<default/>`
|
|
29
|
+
form), or to <c1/> (the first choice, for `=(<c1/>|<c2>/|...)`
|
|
30
|
+
form, or for the list form `=(<c1/>|<c2>/|...)...`),
|
|
31
|
+
or to `false` (for value-less options). Then set
|
|
32
|
+
<getopt-arguments><getopt-args/></getopt-arguments>.
|
|
33
|
+
|
|
34
|
+
Additionally, simulate <getopt-info/> as a comma-separated
|
|
35
|
+
markdown rendering of the parsed options in the form `<longN/>:
|
|
36
|
+
**<valueN/>**, [...]` (joined with `, `, with each value
|
|
37
|
+
shell-quoted if value contains spaces or special characters, and
|
|
38
|
+
excluding the `help` option and any *internal* option whose long
|
|
39
|
+
name starts with `int-`).
|
|
40
|
+
|
|
41
|
+
You then *MUST* silently *SKIP* the steps 3-6 below
|
|
42
|
+
and proceed directly to step 7 to display the results.
|
|
43
|
+
</if>
|
|
35
44
|
|
|
36
45
|
3. **MCP Call**:
|
|
37
46
|
Call the `ase_getopt(name: "<getopt-skill/>", spec:
|