@rse/ase 0.9.54 → 0.9.55
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-log.js +5 -0
- package/dst/ase-service.js +45 -6
- package/dst/ase-task.js +117 -18
- 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 +1 -1
- package/plugin/meta/ase-common-task.md +3 -2
- package/plugin/meta/ase-format-task.md +88 -18
- package/plugin/package.json +1 -1
- package/plugin/skills/ase-code-lint/help.md +2 -2
- package/plugin/skills/ase-meta-config/help.md +3 -3
- package/plugin/skills/ase-meta-quotes/help.md +4 -3
- package/plugin/skills/ase-task-condense/SKILL.md +6 -5
- package/plugin/skills/ase-task-condense/help.md +1 -1
- package/plugin/skills/ase-task-edit/SKILL.md +26 -5
- package/plugin/skills/ase-task-grill/SKILL.md +8 -4
- package/plugin/skills/ase-task-grill/help.md +2 -1
- package/plugin/skills/ase-task-implement/SKILL.md +31 -2
- package/plugin/skills/ase-task-implement/help.md +7 -3
- package/plugin/skills/ase-task-list/SKILL.md +51 -15
- package/plugin/skills/ase-task-list/help.md +49 -6
- package/plugin/skills/ase-task-preflight/SKILL.md +9 -3
- package/plugin/skills/ase-task-preflight/help.md +4 -3
- package/plugin/skills/ase-task-reboot/SKILL.md +1 -1
- package/plugin/skills/ase-task-view/SKILL.md +23 -2
package/dst/ase-log.js
CHANGED
|
@@ -12,6 +12,8 @@ const levels = [
|
|
|
12
12
|
{ name: "info", style: chalk.blue },
|
|
13
13
|
{ name: "debug", style: chalk.green }
|
|
14
14
|
];
|
|
15
|
+
/* check whether an arbitrary string is a valid log level */
|
|
16
|
+
export const isLogLevel = (level) => levels.some((l) => l.name === level);
|
|
15
17
|
export default class Log {
|
|
16
18
|
_program;
|
|
17
19
|
_logLevel;
|
|
@@ -36,11 +38,14 @@ export default class Log {
|
|
|
36
38
|
return stream;
|
|
37
39
|
}
|
|
38
40
|
logLevel(level) {
|
|
41
|
+
if (level === undefined)
|
|
42
|
+
return this._logLevel;
|
|
39
43
|
const idx = levels.findIndex((l) => l.name === level);
|
|
40
44
|
if (idx === -1)
|
|
41
45
|
throw new RangeError(`invalid log level "${level}" (expected one of: ${levels.map((l) => l.name).join(", ")})`);
|
|
42
46
|
this._logLevel = level;
|
|
43
47
|
this.logLevelIdx = idx;
|
|
48
|
+
return this._logLevel;
|
|
44
49
|
}
|
|
45
50
|
logFile(file) {
|
|
46
51
|
if (file === this._logFile)
|
package/dst/ase-service.js
CHANGED
|
@@ -16,6 +16,7 @@ import * as v from "valibot";
|
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
17
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
18
18
|
import { Config, configSchema, ConfigMCP } from "./ase-config.js";
|
|
19
|
+
import { isLogLevel } from "./ase-log.js";
|
|
19
20
|
import { CompatMCP } from "./ase-compat.js";
|
|
20
21
|
import { DiagramMCP } from "./ase-diagram.js";
|
|
21
22
|
import { TaskMCP } from "./ase-task.js";
|
|
@@ -61,11 +62,18 @@ export const probe = async (port, projectId) => {
|
|
|
61
62
|
};
|
|
62
63
|
const SERVE_ENV = "ASE_SERVICE_SERVE";
|
|
63
64
|
const PORT_ENV = "ASE_SERVICE_PORT";
|
|
65
|
+
const LEVEL_ENV = "ASE_SERVICE_LOG_LEVEL";
|
|
64
66
|
const IDLE_MS = 30 * 60 * 1000;
|
|
65
67
|
const TICK_MS = 60 * 1000;
|
|
66
68
|
const PORT_MIN = 42000;
|
|
67
69
|
const PORT_MAX = 44000;
|
|
68
70
|
const PORT_TRIES = 20;
|
|
71
|
+
/* bounds for the append-only ".ase/service.log" file:
|
|
72
|
+
maximum tolerated size, number of tail lines surviving a trim,
|
|
73
|
+
and maximum length of the MCP tool call arguments logged per request */
|
|
74
|
+
const LOG_MAX_SIZE = 1024 * 1024;
|
|
75
|
+
const LOG_KEEP_LINES = 2000;
|
|
76
|
+
const LOG_ARGS_MAX = 200;
|
|
69
77
|
/* load the optional "config.yaml" and "service.yaml" files and derive
|
|
70
78
|
the service identity context (project id, port, service config) */
|
|
71
79
|
export const loadServiceContext = (log) => {
|
|
@@ -130,15 +138,32 @@ export class Service {
|
|
|
130
138
|
svc.write();
|
|
131
139
|
});
|
|
132
140
|
}
|
|
141
|
+
/* trim the log file down to its tail if it grew beyond the size limit */
|
|
142
|
+
static trimLog(logFile) {
|
|
143
|
+
try {
|
|
144
|
+
if (!fs.existsSync(logFile) || fs.statSync(logFile).size <= LOG_MAX_SIZE)
|
|
145
|
+
return;
|
|
146
|
+
const tail = Service.readLogTail(logFile, LOG_KEEP_LINES);
|
|
147
|
+
fs.writeFileSync(logFile, tail.length > 0 ? `${tail}\n` : "");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* intentionally ignore all trimming errors, as a
|
|
151
|
+
non-trimmable log file must never block the service */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
133
154
|
/* spawn the current executable detached as a background service */
|
|
134
|
-
static spawnDetached(aseDir, port) {
|
|
155
|
+
static spawnDetached(aseDir, port, logLevel) {
|
|
135
156
|
fs.mkdirSync(aseDir, { recursive: true });
|
|
136
157
|
const logFile = path.join(aseDir, "service.log");
|
|
158
|
+
/* trim the log before handing it to the service, as the detached
|
|
159
|
+
service inherits the file descriptor for its entire lifetime and
|
|
160
|
+
hence cannot rotate the file itself while it is running */
|
|
161
|
+
Service.trimLog(logFile);
|
|
137
162
|
const fd = fs.openSync(logFile, "a");
|
|
138
163
|
const entry = fileURLToPath(new URL("./ase.js", import.meta.url));
|
|
139
164
|
const child = spawn(process.execPath, [entry, "service", "start"], {
|
|
140
165
|
detached: true,
|
|
141
|
-
env: { ...process.env, [SERVE_ENV]: "1", [PORT_ENV]: String(port) },
|
|
166
|
+
env: { ...process.env, [SERVE_ENV]: "1", [PORT_ENV]: String(port), [LEVEL_ENV]: logLevel },
|
|
142
167
|
stdio: ["ignore", fd, fd]
|
|
143
168
|
});
|
|
144
169
|
fs.closeSync(fd);
|
|
@@ -285,11 +310,20 @@ export default class ServiceCommand {
|
|
|
285
310
|
bodyInfo = ` [${bMethod}]`;
|
|
286
311
|
if (bName !== null) {
|
|
287
312
|
bodyInfo += ` ${bName}`;
|
|
288
|
-
if (bArgs !== null)
|
|
289
|
-
|
|
313
|
+
if (bArgs !== null) {
|
|
314
|
+
/* cap the arguments, as payload-carrying tool calls
|
|
315
|
+
(task plans, key/value batches, etc) would
|
|
316
|
+
otherwise dominate the entire log file */
|
|
317
|
+
const args = JSON.stringify(bArgs);
|
|
318
|
+
bodyInfo += ` ${args.length > LOG_ARGS_MAX ? `${args.slice(0, LOG_ARGS_MAX)}…` : args}`;
|
|
319
|
+
}
|
|
290
320
|
}
|
|
291
321
|
}
|
|
292
|
-
|
|
322
|
+
/* log tool calls regularly, but all remaining MCP traffic
|
|
323
|
+
(session handshakes, notifications, SSE stream opens) at
|
|
324
|
+
debug level only, as it carries no diagnostic value */
|
|
325
|
+
const level = bMethod === "tools/call" ? "info" : "debug";
|
|
326
|
+
this.log.write(level, `mcp: ${request.method.toUpperCase()} ${request.path}${bodyInfo}`);
|
|
293
327
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
294
328
|
const mcp = buildMcpServer();
|
|
295
329
|
request.raw.res.on("close", () => {
|
|
@@ -394,6 +428,11 @@ export default class ServiceCommand {
|
|
|
394
428
|
const ctx = this.loadContext();
|
|
395
429
|
let port = ctx.port;
|
|
396
430
|
if (process.env[SERVE_ENV] === "1") {
|
|
431
|
+
/* adopt the log level of the spawning process, as the
|
|
432
|
+
detached service is started without any CLI options */
|
|
433
|
+
const level = process.env[LEVEL_ENV];
|
|
434
|
+
if (level !== undefined && isLogLevel(level))
|
|
435
|
+
this.log.logLevel(level);
|
|
397
436
|
const raw = process.env[PORT_ENV];
|
|
398
437
|
port = raw !== undefined ? Number(raw) : await Service.allocatePort();
|
|
399
438
|
await this.runService({ ...ctx, port });
|
|
@@ -411,7 +450,7 @@ export default class ServiceCommand {
|
|
|
411
450
|
let lastErr = new Error("service failed to start within timeout");
|
|
412
451
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
413
452
|
port = await Service.allocatePort();
|
|
414
|
-
const { child, logFile } = Service.spawnDetached(ctx.aseDir, port);
|
|
453
|
+
const { child, logFile } = Service.spawnDetached(ctx.aseDir, port, this.log.logLevel());
|
|
415
454
|
let exited = false;
|
|
416
455
|
let exitCode = null;
|
|
417
456
|
let resolveExit = () => { };
|
package/dst/ase-task.js
CHANGED
|
@@ -14,6 +14,12 @@ import { LRUCache } from "lru-cache";
|
|
|
14
14
|
import { Config, configSchema, parseScope } from "./ase-config.js";
|
|
15
15
|
import { Markdown } from "./ase-markdown.js";
|
|
16
16
|
import { readStdin, writeStdout } from "./ase-stdio.js";
|
|
17
|
+
/* the lifecycle states a task plan can be in, i.e., the accepted
|
|
18
|
+
values of the "Status:" frontmatter key of a task plan */
|
|
19
|
+
export const taskStates = [
|
|
20
|
+
"DRAFTED", "REJECTED", "APPROVED", "DEFERRED",
|
|
21
|
+
"STARTED", "BLOCKED", "COMPLETED", "CANCELLED"
|
|
22
|
+
];
|
|
17
23
|
/* reusable functionality: persisted task plans under
|
|
18
24
|
<project>/<basedir>/TASK-<id>.md (driven by the
|
|
19
25
|
"project.artifact.task.{basedir,files}" configuration) */
|
|
@@ -141,12 +147,54 @@ export class Task {
|
|
|
141
147
|
migrated.sort((a, b) => a.localeCompare(b));
|
|
142
148
|
return migrated;
|
|
143
149
|
}
|
|
144
|
-
/*
|
|
150
|
+
/* the legacy task plan header lines, each mapped onto the
|
|
151
|
+
frontmatter key which superseded it */
|
|
152
|
+
static legacy = [
|
|
153
|
+
{ key: "Created", re: /^⎈[ \t]+Created:[ \t]*(.*)$/m },
|
|
154
|
+
{ key: "Modified", re: /^⚙[ \t]+Modified:[ \t]*(.*)$/m },
|
|
155
|
+
{ key: "Kind", re: /^☯[ \t]+Kind:[ \t]*(.*)$/m }
|
|
156
|
+
];
|
|
157
|
+
/* render a single frontmatter line with a column-aligned key */
|
|
158
|
+
static frontLine(key, value) {
|
|
159
|
+
return (key + ":").padEnd(12) + value;
|
|
160
|
+
}
|
|
161
|
+
/* normalize a legacy task plan -- one carrying its metadata in the
|
|
162
|
+
"# TASK <id>: <title>" heading and the "⎈"/"⚙"/"☯" glyph header
|
|
163
|
+
lines -- into the current Markdown frontmatter shape, so every
|
|
164
|
+
consumer sees a single plan shape only; a plan already carrying a
|
|
165
|
+
frontmatter block, and any content without a task heading at all,
|
|
166
|
+
is passed through verbatim -- in particular, absent optional keys
|
|
167
|
+
are never materialized, as they read as their default value */
|
|
168
|
+
static normalize(id, text) {
|
|
169
|
+
if (text === "" || /^---\r?\n/.test(text))
|
|
170
|
+
return text;
|
|
171
|
+
const heading = /^#[ \t]+TASK(?:[ \t]+[A-Za-z0-9_-]+)?[ \t]*:[ \t]*(.*)$/m.exec(text);
|
|
172
|
+
if (heading === null)
|
|
173
|
+
return text;
|
|
174
|
+
/* lift the glyph header lines into their frontmatter keys, with
|
|
175
|
+
the task id taken from the authoritative filename-derived id */
|
|
176
|
+
let body = text.replace(heading[0], "");
|
|
177
|
+
const front = [Task.frontLine("Id", id)];
|
|
178
|
+
for (const legacy of Task.legacy) {
|
|
179
|
+
const m = legacy.re.exec(body);
|
|
180
|
+
if (m === null)
|
|
181
|
+
continue;
|
|
182
|
+
front.push(Task.frontLine(legacy.key, m[1].trim()));
|
|
183
|
+
body = body.replace(m[0], "");
|
|
184
|
+
}
|
|
185
|
+
/* re-assemble the plan from the frontmatter block, the reduced
|
|
186
|
+
heading, and the body stripped of its now leading blank lines */
|
|
187
|
+
return `---\n${front.join("\n")}\n---\n\n` +
|
|
188
|
+
`# TASK: ${heading[1].trim()}\n\n` +
|
|
189
|
+
body.replace(/^(?:[ \t]*\r?\n)+/, "");
|
|
190
|
+
}
|
|
191
|
+
/* load a task, normalized into the current Markdown frontmatter
|
|
192
|
+
shape; returns empty string if no task exists */
|
|
145
193
|
static load(log, id) {
|
|
146
194
|
const file = Task.path(log, id);
|
|
147
195
|
if (!fs.existsSync(file))
|
|
148
196
|
return "";
|
|
149
|
-
return fs.readFileSync(file, "utf8");
|
|
197
|
+
return Task.normalize(id, fs.readFileSync(file, "utf8"));
|
|
150
198
|
}
|
|
151
199
|
/* save a task as UTF-8 text under the given id into the
|
|
152
200
|
<project>/<basedir>/TASK-<id>.md file */
|
|
@@ -167,10 +215,11 @@ export class Task {
|
|
|
167
215
|
return true;
|
|
168
216
|
}
|
|
169
217
|
/* rename a task by moving its <project>/<basedir>/TASK-<oldId>.md file
|
|
170
|
-
to <project>/<basedir>/TASK-<newId>.md; the embedded
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
not
|
|
218
|
+
to <project>/<basedir>/TASK-<newId>.md; the embedded "Id:"
|
|
219
|
+
frontmatter key inside the plan content is rewritten to the new id
|
|
220
|
+
(falling back to the "# TASK <id>:" heading of a still legacy,
|
|
221
|
+
not yet normalized plan); returns true on success, false if the
|
|
222
|
+
source task does not exist; throws if the target id already exists */
|
|
174
223
|
static rename(log, oldId, newId) {
|
|
175
224
|
const oldFile = Task.path(log, oldId);
|
|
176
225
|
const newFile = Task.path(log, newId);
|
|
@@ -179,7 +228,9 @@ export class Task {
|
|
|
179
228
|
if (fs.existsSync(newFile))
|
|
180
229
|
throw new Error(`task: target id "${newId}" already exists`);
|
|
181
230
|
const text = fs.readFileSync(oldFile, "utf8");
|
|
182
|
-
const updated = text
|
|
231
|
+
const updated = /^---\r?\n/.test(text) ?
|
|
232
|
+
text.replace(/^(Id:[ \t]*)[A-Za-z0-9_-]+[ \t]*$/m, `$1${newId}`) :
|
|
233
|
+
text.replace(/(^#\s+TASK\s+)[A-Za-z0-9_-]+(\s*:)/m, `$1${newId}$2`);
|
|
183
234
|
fs.mkdirSync(path.dirname(newFile), { recursive: true });
|
|
184
235
|
fs.writeFileSync(newFile, updated, "utf8");
|
|
185
236
|
fs.rmSync(oldFile, { force: true });
|
|
@@ -207,17 +258,55 @@ export class Task {
|
|
|
207
258
|
}
|
|
208
259
|
return out;
|
|
209
260
|
}
|
|
210
|
-
/*
|
|
211
|
-
|
|
212
|
-
|
|
261
|
+
/* read the "Status:" frontmatter key of a task plan file, falling back
|
|
262
|
+
to the "DRAFTED" default of the task plan format for a plan whose
|
|
263
|
+
frontmatter is absent or carries no such key */
|
|
264
|
+
static status(file) {
|
|
265
|
+
const fm = /^---\r?\n([\s\S]*?\r?\n)---\r?\n/.exec(fs.readFileSync(file, "utf8"));
|
|
266
|
+
if (fm === null)
|
|
267
|
+
return "DRAFTED";
|
|
268
|
+
const m = /^Status:[ \t]*(\S+)[ \t]*$/m.exec(fm[1]);
|
|
269
|
+
if (m === null)
|
|
270
|
+
return "DRAFTED";
|
|
271
|
+
return m[1];
|
|
272
|
+
}
|
|
273
|
+
/* list all persisted tasks in lexicographic id order, each with the
|
|
274
|
+
`status` of its plan; if verbose is true, each entry's `mtime` is
|
|
275
|
+
set to the task file's modification time formatted as
|
|
276
|
+
"YYYY-MM-DD HH:MM", otherwise it is left undefined */
|
|
213
277
|
static list(log, verbose = false) {
|
|
214
278
|
const out = Task.scan(log).map((entry) => ({
|
|
215
279
|
id: entry.id,
|
|
280
|
+
status: Task.status(entry.file),
|
|
216
281
|
mtime: verbose ? DateTime.fromJSDate(entry.st.mtime).toFormat("yyyy-LL-dd HH:mm") : undefined
|
|
217
282
|
}));
|
|
218
283
|
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
219
284
|
return out;
|
|
220
285
|
}
|
|
286
|
+
/* resolve an "include" and an "exclude" comma-separated lifecycle
|
|
287
|
+
state list into the effective state set a task plan has to be in
|
|
288
|
+
to be listed at all; the "none" sentinel and empty tokens are
|
|
289
|
+
silently dropped, an empty "include" list means all states, and
|
|
290
|
+
the "exclude" list is applied after the "include" list */
|
|
291
|
+
static states(include, exclude) {
|
|
292
|
+
const parse = (list) => list.split(",")
|
|
293
|
+
.map((token) => token.trim())
|
|
294
|
+
.filter((token) => token !== "" && token.toUpperCase() !== "NONE")
|
|
295
|
+
.map((token) => {
|
|
296
|
+
const state = token.toUpperCase();
|
|
297
|
+
if (!taskStates.includes(state))
|
|
298
|
+
throw new Error(`task: invalid state "${token}" ` +
|
|
299
|
+
`(expected one of: ${taskStates.join(", ")})`);
|
|
300
|
+
return state;
|
|
301
|
+
});
|
|
302
|
+
const included = parse(include);
|
|
303
|
+
const excluded = parse(exclude);
|
|
304
|
+
const states = (included.length > 0 ? included : taskStates)
|
|
305
|
+
.filter((state) => !excluded.includes(state));
|
|
306
|
+
if (states.length === 0)
|
|
307
|
+
throw new Error("task: options \"--include\" and \"--exclude\" cancel out to an empty state set");
|
|
308
|
+
return states;
|
|
309
|
+
}
|
|
221
310
|
/* purge tasks whose modification time is older than the given cutoff in
|
|
222
311
|
milliseconds; returns the list of removed task ids */
|
|
223
312
|
static purge(log, maxAgeMs) {
|
|
@@ -274,12 +363,19 @@ export default class TaskCommand {
|
|
|
274
363
|
task
|
|
275
364
|
.command("list")
|
|
276
365
|
.description("List all persisted task ids, one per line")
|
|
277
|
-
.option("-v, --verbose", "also show the task
|
|
366
|
+
.option("-v, --verbose", "also show the task plan status and the task file " +
|
|
367
|
+
"modification time as (YYYY-MM-DD HH:MM)")
|
|
368
|
+
.option("-i, --include <states>", "comma-separated list of lifecycle states to list " +
|
|
369
|
+
`(${taskStates.join("|")}), or "none" for no restriction`, "none")
|
|
370
|
+
.option("-e, --exclude <states>", "comma-separated list of lifecycle states to not list " +
|
|
371
|
+
`(${taskStates.join("|")}), or "none" for no exclusion`, "COMPLETED,CANCELLED")
|
|
278
372
|
.action(async (opts) => {
|
|
279
|
-
const
|
|
373
|
+
const states = Task.states(opts.include, opts.exclude);
|
|
374
|
+
const items = Task.list(this.log, opts.verbose ?? false)
|
|
375
|
+
.filter((item) => states.includes(item.status));
|
|
280
376
|
for (const item of items) {
|
|
281
377
|
if (opts.verbose)
|
|
282
|
-
await writeStdout(`${item.id}\t(${item.mtime})\n`);
|
|
378
|
+
await writeStdout(`${item.id}\t${item.status}\t(${item.mtime})\n`);
|
|
283
379
|
else
|
|
284
380
|
await writeStdout(`${item.id}\n`);
|
|
285
381
|
}
|
|
@@ -395,7 +491,8 @@ export class TaskMCP {
|
|
|
395
491
|
title: "ASE task list",
|
|
396
492
|
description: "List all persisted tasks. " +
|
|
397
493
|
"Returns a `tasks` array (in lexicographic `id` order) where each item has the " +
|
|
398
|
-
"task `id
|
|
494
|
+
"task `id` and the `status` of its plan (the `Status:` frontmatter key, defaulting " +
|
|
495
|
+
"to `DRAFTED`). If `verbose` is `true`, each item additionally has an `mtime` field " +
|
|
399
496
|
"(last modification time of the task's `TASK-<id>.md` file, formatted as `YYYY-MM-DD HH:MM`). " +
|
|
400
497
|
"Returns an empty array if no tasks exist.",
|
|
401
498
|
inputSchema: {
|
|
@@ -405,6 +502,7 @@ export class TaskMCP {
|
|
|
405
502
|
outputSchema: {
|
|
406
503
|
tasks: z.array(z.object({
|
|
407
504
|
id: z.string().describe("task identifier"),
|
|
505
|
+
status: z.string().describe("task plan lifecycle status (`Status:` frontmatter key, default `DRAFTED`)"),
|
|
408
506
|
mtime: z.string().optional()
|
|
409
507
|
.describe("`TASK-<id>.md` modification time (`YYYY-MM-DD HH:MM`); only present if `verbose` is true")
|
|
410
508
|
})).describe("all persisted tasks in lexicographic id order")
|
|
@@ -414,8 +512,8 @@ export class TaskMCP {
|
|
|
414
512
|
const verbose = args.verbose ?? false;
|
|
415
513
|
const items = Task.list(this.log, verbose);
|
|
416
514
|
const tasks = verbose ?
|
|
417
|
-
items.map((item) => ({ id: item.id, mtime: item.mtime ?? "" })) :
|
|
418
|
-
items.map((item) => ({ id: item.id }));
|
|
515
|
+
items.map((item) => ({ id: item.id, status: item.status, mtime: item.mtime ?? "" })) :
|
|
516
|
+
items.map((item) => ({ id: item.id, status: item.status }));
|
|
419
517
|
const result = { tasks };
|
|
420
518
|
return {
|
|
421
519
|
structuredContent: result,
|
|
@@ -430,7 +528,8 @@ export class TaskMCP {
|
|
|
430
528
|
mcp.registerTool("ase_task_load", {
|
|
431
529
|
title: "ASE task load",
|
|
432
530
|
description: "Load a previously persisted task by `id`. " +
|
|
433
|
-
"Returns the task as `text
|
|
531
|
+
"Returns the task as `text`, normalized into the current Markdown frontmatter shape; " +
|
|
532
|
+
"returns an empty string if no task exists for the `id`.",
|
|
434
533
|
inputSchema: {
|
|
435
534
|
id: z.string()
|
|
436
535
|
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '_', '-')")
|
|
@@ -501,7 +600,7 @@ export class TaskMCP {
|
|
|
501
600
|
mcp.registerTool("ase_task_rename", {
|
|
502
601
|
title: "ASE task rename",
|
|
503
602
|
description: "Rename a previously persisted task from `old` to `new` by moving the " +
|
|
504
|
-
"task `TASK-<id>.md` file and rewriting its embedded
|
|
603
|
+
"task `TASK-<id>.md` file and rewriting its embedded `Id:` frontmatter key. " +
|
|
505
604
|
"Returns a status `text` indicating whether the rename succeeded. " +
|
|
506
605
|
"Fails with an error if the target id already exists.",
|
|
507
606
|
inputSchema: {
|
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.55",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -15,7 +15,7 @@ and updating of code. Do not output anything.
|
|
|
15
15
|
Determine the *kind of change* the task plan describes and internalize
|
|
16
16
|
the corresponding tenet sets:
|
|
17
17
|
|
|
18
|
-
- If <task-content/>
|
|
18
|
+
- If the frontmatter of <task-content/> carries a `Kind: <text/>` key
|
|
19
19
|
and <text/> is one of `CRAFTING`, `REFACTORING`, or `RESOLVING`:
|
|
20
20
|
Set <task-kind><text/></task-kind> (set task kind to the stated kind).
|
|
21
21
|
|
|
@@ -103,11 +103,12 @@ format, which has to be determined by calling the
|
|
|
103
103
|
`ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase`
|
|
104
104
|
MCP server and use the `text` field of its response. If
|
|
105
105
|
<timestamp-created/> is still unset (because the plan content
|
|
106
|
-
had no `Created:`
|
|
106
|
+
had no `Created:` frontmatter key), set
|
|
107
107
|
<timestamp-created><timestamp-modified/></timestamp-created>
|
|
108
108
|
(fall back to the modified timestamp). Re-insert the current
|
|
109
109
|
<ase-task-id/>, the original <timestamp-created/>, and the
|
|
110
|
-
refreshed <timestamp-modified/> into
|
|
110
|
+
refreshed <timestamp-modified/> into the frontmatter keys `Id:`,
|
|
111
|
+
`Created:`, and `Modified:` of <task-content/> and calculate
|
|
111
112
|
the number of words <words/> of <task-content/>.
|
|
112
113
|
|
|
113
114
|
Call the `ase_task_save(id: "<ase-task-id/>", text:
|
|
@@ -5,12 +5,16 @@ Task
|
|
|
5
5
|
Every *task* uses a strict and fixed format:
|
|
6
6
|
|
|
7
7
|
<format>
|
|
8
|
+
---
|
|
9
|
+
Id: <task-id/>
|
|
10
|
+
Created: <timestamp-created/>
|
|
11
|
+
Modified: <timestamp-modified/>
|
|
12
|
+
Status: <task-status/>
|
|
13
|
+
Properties: <task-properties/>
|
|
14
|
+
Kind: <task-kind/>
|
|
15
|
+
---
|
|
8
16
|
|
|
9
|
-
# TASK <
|
|
10
|
-
|
|
11
|
-
⎈ Created: <timestamp-created/>
|
|
12
|
-
⚙ Modified: <timestamp-modified/>
|
|
13
|
-
☯ Kind: <task-kind/>
|
|
17
|
+
# TASK: <title/>
|
|
14
18
|
|
|
15
19
|
## CONTEXT
|
|
16
20
|
|
|
@@ -34,8 +38,19 @@ Every *task* uses a strict and fixed format:
|
|
|
34
38
|
|
|
35
39
|
You *MUST* honor the following hints on this *task* format:
|
|
36
40
|
|
|
37
|
-
-
|
|
38
|
-
|
|
41
|
+
- The content *MUST* begin with the `---` opening delimiter of the
|
|
42
|
+
*Markdown frontmatter* as its very *first* line -- there is *no*
|
|
43
|
+
leading empty line, as any line before the `---` would degrade the
|
|
44
|
+
frontmatter into ordinary Markdown. You *MUST* always keep the empty
|
|
45
|
+
line between the closing `---` delimiter and the `#` heading, and
|
|
46
|
+
always keep the last empty line. If one of them is missing, add it
|
|
47
|
+
back.
|
|
48
|
+
|
|
49
|
+
- The *frontmatter* carries the keys `Id`, `Created`, `Modified`,
|
|
50
|
+
`Status`, `Properties`, and `Kind`, in exactly this order, with their
|
|
51
|
+
values being *unquoted* plain scalars and vertically aligned one
|
|
52
|
+
space after the longest key. Only `Id` is *mandatory* -- every other
|
|
53
|
+
key is *optional* and, when absent, falls back to its default value.
|
|
39
54
|
|
|
40
55
|
- In all descriptions, highlight *code* as
|
|
41
56
|
<template>`<code/>`</template> and *key aspects* as
|
|
@@ -47,25 +62,80 @@ You *MUST* honor the following hints on this *task* format:
|
|
|
47
62
|
is changed, what benefit results or what the rationale is behind the
|
|
48
63
|
change.
|
|
49
64
|
|
|
50
|
-
- The <task-id/>
|
|
51
|
-
<ase-task-id/> in the current session
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
- The <task-id/> of the `Id:` frontmatter key has to be substituted
|
|
66
|
+
with the current value of <ase-task-id/> in the current session
|
|
67
|
+
context.
|
|
68
|
+
|
|
69
|
+
- The `Status:` frontmatter key states the current *lifecycle state*
|
|
70
|
+
of the task plan. The key is *optional* and defaults to `DRAFTED`:
|
|
71
|
+
an *absent* key reads as `DRAFTED`, and a *newly created* plan
|
|
72
|
+
carries `DRAFTED` explicitly. The <task-status/> value is *strictly*
|
|
73
|
+
one of the following eight states:
|
|
74
|
+
|
|
75
|
+
- `DRAFTED`: plan exists but is still provisional and not yet
|
|
76
|
+
cleared for implementation.
|
|
77
|
+
- `REJECTED`: plan was reviewed and refused, and has to be reworked
|
|
78
|
+
before it can be approved.
|
|
79
|
+
- `APPROVED`: plan is accepted as authoritative and cleared for
|
|
80
|
+
implementation, but no work has begun.
|
|
81
|
+
- `DEFERRED`: plan is approved and unobstructed, but its start was
|
|
82
|
+
deliberately postponed.
|
|
83
|
+
- `STARTED`: implementation of the plan is actively underway.
|
|
84
|
+
- `BLOCKED`: implementation is halted by an impediment which
|
|
85
|
+
someone has to remove before it can resume.
|
|
86
|
+
- `COMPLETED`: plan was implemented in full and reached its
|
|
87
|
+
intended outcome.
|
|
88
|
+
- `CANCELLED`: plan was terminated before completion, because it
|
|
89
|
+
failed, was called off, or became obsolete.
|
|
90
|
+
|
|
91
|
+
- The eight states form a *state machine*. Whoever sets the `Status:`
|
|
92
|
+
key *MUST* only move along one of the following transitions, whereby
|
|
93
|
+
a *single* operation *MAY* traverse *several* transitions at once if
|
|
94
|
+
it performs the corresponding stages in one go:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
DRAFTED ──reject───▶ REJECTED STARTED ──block────▶ BLOCKED
|
|
98
|
+
REJECTED ──redraft──▶ DRAFTED BLOCKED ──unblock──▶ STARTED
|
|
99
|
+
DRAFTED ──approve──▶ APPROVED STARTED ──complete─▶ COMPLETED
|
|
100
|
+
APPROVED ──defer────▶ DEFERRED
|
|
101
|
+
DEFERRED ──resume───▶ APPROVED any non-terminal state
|
|
102
|
+
APPROVED ──start────▶ STARTED ──cancel───▶ CANCELLED
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`COMPLETED` and `CANCELLED` are the two *terminal* states: a plan
|
|
106
|
+
which reached one of them is *finished* and leaves the state machine.
|
|
107
|
+
|
|
108
|
+
- The `Properties:` frontmatter key states which *stages* the task
|
|
109
|
+
plan already passed through. The <task-properties/> value is
|
|
110
|
+
*strictly* either `none` or a comma-separated list of the values
|
|
111
|
+
`grilled`, `preflighted`, `implemented`, and `verified`, listed in
|
|
112
|
+
exactly this order, each at most once. The key is *optional* and
|
|
113
|
+
defaults to `none`: an *absent* key reads as `none`, and a *newly
|
|
114
|
+
created* plan carries `none` explicitly. The list only ever
|
|
115
|
+
*accumulates*: a skill *adds* its own value if still absent and
|
|
116
|
+
*MUST NOT* drop any value already present, except when a plan is
|
|
117
|
+
*recreated from scratch*, which resets it to `none`.
|
|
118
|
+
|
|
119
|
+
- The `Kind:` frontmatter key states the *kind of change* the task plan
|
|
54
120
|
describes, and hence which *operation-specific tenet set* of the
|
|
55
121
|
**ASE Tenets** a subsequent preflight or implementation has to
|
|
56
122
|
honor. The <task-kind/> value is *strictly* one of `CRAFTING`,
|
|
57
|
-
`REFACTORING`, or `RESOLVING
|
|
58
|
-
the `⎈ Created:` and `⚙ Modified:` lines above it.
|
|
123
|
+
`REFACTORING`, or `RESOLVING`.
|
|
59
124
|
|
|
60
|
-
- The
|
|
61
|
-
*updating* a task plan *CAN* update an already present
|
|
125
|
+
- The `Kind:` frontmatter key is *optional*: a skill *authoring* or
|
|
126
|
+
*updating* a task plan *CAN* update an already present key or pass
|
|
62
127
|
it through *verbatim* and *MAY* create a missing one by *inferring*
|
|
63
128
|
the kind from the plan content (defaulting to `CRAFTING`). A
|
|
64
|
-
`--dry` run *never* drops this
|
|
129
|
+
`--dry` run *never* drops this key, as `--dry` only omits the
|
|
65
130
|
`## VERIFICATION` section.
|
|
66
131
|
|
|
67
|
-
-
|
|
68
|
-
|
|
132
|
+
- A skill *writing* an optional key which is still *absent* inserts it
|
|
133
|
+
at its position in the key order above and re-aligns the values of
|
|
134
|
+
the whole frontmatter block.
|
|
135
|
+
|
|
136
|
+
- The <timestamp-created/> of the `Created:` frontmatter key is the
|
|
137
|
+
timestamp when this task plan was created. The <timestamp-modified/>
|
|
138
|
+
of the `Modified:` frontmatter key is the timestamp when this
|
|
69
139
|
task plan was last modified. Both use an ISO-style format
|
|
70
140
|
value. The value of both can be determined by a call to the
|
|
71
141
|
`ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase` MCP
|
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.55",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -64,8 +64,8 @@ A05 COMPLEXITY A10 SMELLS A15 PERFORMANCE A20 DEAD-CODE
|
|
|
64
64
|
|
|
65
65
|
`--exclude`|`-e`=*aspect*[`,`...]:
|
|
66
66
|
Remove the given comma-separated list of aspect ids from the checked
|
|
67
|
-
code quality aspects. Applied *after* `--include`, so
|
|
68
|
-
A02` checks `A01` only.
|
|
67
|
+
code quality aspects. Applied *after* `--include`, so
|
|
68
|
+
`-i A01,A02 -e A02` checks `A01` only.
|
|
69
69
|
|
|
70
70
|
## ARGUMENTS
|
|
71
71
|
|
|
@@ -67,9 +67,9 @@ The following *operations* exist:
|
|
|
67
67
|
The `init` and `edit` subcommands of the `ase config` CLI are
|
|
68
68
|
deliberately *not* mirrored: `edit` is bound to the interactive `$EDITOR`
|
|
69
69
|
and therefore has no meaning inside an assistant turn, and `init` is a
|
|
70
|
-
preset-bootstrapping operation that stays a shell concern next to
|
|
71
|
-
setup`. Both remain available as `ase config init` and
|
|
72
|
-
on the command line.
|
|
70
|
+
preset-bootstrapping operation that stays a shell concern next to
|
|
71
|
+
`ase setup`. Both remain available as `ase config init` and
|
|
72
|
+
`ase config edit` on the command line.
|
|
73
73
|
|
|
74
74
|
## OPTIONS
|
|
75
75
|
|
|
@@ -39,9 +39,10 @@ without any quote is rendered as `(none)`.
|
|
|
39
39
|
|
|
40
40
|
Every quote is rendered on its own line and carries, where applicable,
|
|
41
41
|
the suffixes `— <author>, <origin>` (in the two `ATTRIBUTED`
|
|
42
|
-
quadrants, omitting whichever part is unknown),
|
|
43
|
-
<source-topic>]` (when the quote was contributed by a
|
|
44
|
-
under `--proximity` instead of by *topic-keywords*
|
|
42
|
+
quadrants, omitting whichever part is unknown),
|
|
43
|
+
`[from proximity: <source-topic>]` (when the quote was contributed by a
|
|
44
|
+
neighborhood topic under `--proximity` instead of by *topic-keywords*
|
|
45
|
+
itself), and
|
|
45
46
|
`(unverified)` (when the exact wording or the attribution could not be
|
|
46
47
|
established with confidence). The `(unverified)` marker is dropped as
|
|
47
48
|
soon as the Internet/Web search under `--ground` confirms the wording and
|
|
@@ -98,7 +98,7 @@ Set <args></args> (set args to empty).
|
|
|
98
98
|
Set <words-before><words/></words-before> (remember the loaded
|
|
99
99
|
word count for the strictly-smaller check in step 3).
|
|
100
100
|
|
|
101
|
-
<if condition="<task-content/>
|
|
101
|
+
<if condition="the frontmatter of <task-content/> carries a `Created: <text/>` key">
|
|
102
102
|
Set <timestamp-created><text/></timestamp-created> (extract the
|
|
103
103
|
original creation timestamp so it can be re-inserted unchanged
|
|
104
104
|
into the condensed <task-content/> in step 3).
|
|
@@ -125,10 +125,11 @@ Set <args></args> (set args to empty).
|
|
|
125
125
|
and unchanged*. Honor the following ruleset *strictly*:
|
|
126
126
|
|
|
127
127
|
1. *Preserve-exactly (never alter)*: the plan <format/>
|
|
128
|
-
structure (the
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
structure (the frontmatter block with its `Id:`, `Created:`,
|
|
129
|
+
`Modified:`, `Status:`, `Properties:`, and `Kind:` keys,
|
|
130
|
+
the headings `#`/`##`, all
|
|
131
|
+
three `## CONTEXT`, `## CHANGES`, and `## VERIFICATION`
|
|
132
|
+
sections, and the `- **<aspect/>**:` bullet labels), all *code spans* and
|
|
132
133
|
code blocks, technical terms, file paths, identifiers,
|
|
133
134
|
numbers, severities (`LOW`/`MEDIUM`/`HIGH`/`ACCEPTED`), and
|
|
134
135
|
the `*<aspect/>*` emphasis highlighting convention.
|
|
@@ -30,7 +30,7 @@ telegrapher-like even under the `writer` persona.
|
|
|
30
30
|
|
|
31
31
|
The plan is saved *only* when condensing actually makes it smaller; if no
|
|
32
32
|
further reduction is possible, the plan is left untouched (including its
|
|
33
|
-
|
|
33
|
+
`Modified:` frontmatter timestamp) and reported as *already condensed*.
|
|
34
34
|
|
|
35
35
|
After condensing, the user is asked whether to stop or hand off to
|
|
36
36
|
`ase-task-edit`, `ase-task-implement`, or `ase-task-preflight`, unless
|
|
@@ -322,13 +322,13 @@ Set <args></args> (set args to empty).
|
|
|
322
322
|
`PREFLIGHT`, or declines/cancels in the dialog of step 3.4:
|
|
323
323
|
|
|
324
324
|
1. *Update timestamp*:
|
|
325
|
-
<if condition="<task-content/>
|
|
325
|
+
<if condition="the frontmatter of <task-content/> carries a `Modified:` key AND <task-content-dirty/> is 'true'">
|
|
326
326
|
Update <timestamp-modified/> with the current time in
|
|
327
327
|
ISO-style format, which has to be determined by calling the
|
|
328
328
|
`ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase`
|
|
329
329
|
MCP server and use the `text` field of its response. Update
|
|
330
|
-
the
|
|
331
|
-
|
|
330
|
+
the `Modified: ...` frontmatter key of <task-content/> with the
|
|
331
|
+
new <timestamp-modified/> value.
|
|
332
332
|
Do not output anything.
|
|
333
333
|
</if>
|
|
334
334
|
|
|
@@ -346,6 +346,27 @@ Set <args></args> (set args to empty).
|
|
|
346
346
|
</if>
|
|
347
347
|
|
|
348
348
|
3. *Render plan*: Treat <task-content/> as *verbatim* Markdown.
|
|
349
|
+
|
|
350
|
+
For the *rendering only*, drop the leading *frontmatter* block --
|
|
351
|
+
both `---` delimiters and all of their keys -- and instead place
|
|
352
|
+
the following column-aligned glyph lines *before* the
|
|
353
|
+
`# TASK: <title/>` heading, separated from it by an empty line,
|
|
354
|
+
omitting the line of every key absent from the frontmatter. The
|
|
355
|
+
glyph lines *MUST* stay *above* the heading, exactly where the
|
|
356
|
+
frontmatter block sits in the plan file, and *MUST NOT* be moved
|
|
357
|
+
below it. This keeps the `---` delimiters from rendering as a
|
|
358
|
+
horizontal rule plus a *setext heading*. This rewrite is
|
|
359
|
+
*display-only* and *MUST NOT* change <task-content/> itself:
|
|
360
|
+
|
|
361
|
+
<format>
|
|
362
|
+
◉ **Id:** <task-id/>
|
|
363
|
+
⎈ **Created:** <timestamp-created/>
|
|
364
|
+
⚙ **Modified:** <timestamp-modified/>
|
|
365
|
+
◐ **Status:** <task-status/>
|
|
366
|
+
⚑ **Properties:** <task-properties/>
|
|
367
|
+
☯ **Kind:** <task-kind/>
|
|
368
|
+
</format>
|
|
369
|
+
|
|
349
370
|
Only output the following <template/>, so the user
|
|
350
371
|
can read the plan and react to it. If <task-content/> is longer
|
|
351
372
|
than 90 lines and a `## IMPLEMENTATION DRAFT` section (from the
|
|
@@ -355,9 +376,9 @@ Set <args></args> (set args to empty).
|
|
|
355
376
|
Use the following <template/>:
|
|
356
377
|
|
|
357
378
|
<template>
|
|
358
|
-
<ase-tpl-head title="TASK"/>
|
|
379
|
+
<ase-tpl-head title="TASK" subtitle="<task-id/>"/>
|
|
359
380
|
<task-content/>
|
|
360
|
-
<ase-tpl-foot title="TASK"/>
|
|
381
|
+
<ase-tpl-foot title="TASK" subtitle="<task-id/>"/>
|
|
361
382
|
</template>
|
|
362
383
|
|
|
363
384
|
4. *Determine next step*:
|
|
@@ -216,7 +216,7 @@ Set <args>--int-reuse-task</args>.
|
|
|
216
216
|
|
|
217
217
|
2. Finally, update the plan in <plan/> based on all answers <answer-N/>.
|
|
218
218
|
|
|
219
|
-
3. <if condition="<plan/>
|
|
219
|
+
3. <if condition="the frontmatter of <plan/> carries a `Created: <text/>` key">
|
|
220
220
|
Set <timestamp-created><text/></timestamp-created> (set
|
|
221
221
|
timestamp-created to extracted text)
|
|
222
222
|
</if>
|
|
@@ -225,11 +225,15 @@ Set <args>--int-reuse-task</args>.
|
|
|
225
225
|
`ase` MCP server and use the `text` field of its response for
|
|
226
226
|
<timestamp-modified/> information. If <timestamp-created/> is
|
|
227
227
|
still unset (because the previous <plan/> had no `Created:`
|
|
228
|
-
|
|
228
|
+
frontmatter key), set <timestamp-created><timestamp-modified/></timestamp-created>
|
|
229
229
|
(fall back to the modified timestamp). Then insert the current
|
|
230
230
|
<ase-task-id/>, previous <timestamp-created/>, and refreshed
|
|
231
|
-
<timestamp-modified/> information
|
|
232
|
-
|
|
231
|
+
<timestamp-modified/> information into the frontmatter keys `Id:`,
|
|
232
|
+
`Created:`, and `Modified:` and calculate the number of
|
|
233
|
+
words <words/> of <plan/>. Additionally *add* the value `grilled`
|
|
234
|
+
to the `Properties:` frontmatter key if it is still absent,
|
|
235
|
+
keeping all already present values and *creating* the whole key
|
|
236
|
+
(with the single value `grilled`) if the plan carries none.
|
|
233
237
|
|
|
234
238
|
5. Call the `ase_task_save(id: "<ase-task-id/>",
|
|
235
239
|
text: "<plan/>")` tool of the `ase` MCP server to save the updated
|
|
@@ -24,7 +24,8 @@ the code base and world knowledge), marks the current-plan choice, and
|
|
|
24
24
|
lets the user pick via an interactive dialog. It honors checks for
|
|
25
25
|
*fuzzy language*, *conflicting terminology*, *conflicting code*, and
|
|
26
26
|
*non-concrete scenarios*. Once all aspects are resolved, the plan is
|
|
27
|
-
updated and persisted,
|
|
27
|
+
updated and persisted, its `Properties:` frontmatter key gains the value
|
|
28
|
+
`grilled`, and the user is offered a hand-off to editing,
|
|
28
29
|
implementation, or preflight.
|
|
29
30
|
|
|
30
31
|
## OPTIONS
|
|
@@ -189,13 +189,42 @@ Procedure
|
|
|
189
189
|
way.
|
|
190
190
|
</if>
|
|
191
191
|
|
|
192
|
-
2.
|
|
192
|
+
2. Update the frontmatter of <task-content/> as follows, *creating*
|
|
193
|
+
each of the `Properties:`, `Status:`, and `Modified:` keys the
|
|
194
|
+
plan does not carry yet at its position in the key order of the
|
|
195
|
+
plan <format/>:
|
|
196
|
+
|
|
197
|
+
- *Add* the value `implemented` to the `Properties:` key if it
|
|
198
|
+
is still absent, keeping all already present values.
|
|
199
|
+
|
|
200
|
+
- *Add* the value `verified` to the `Properties:` key as well,
|
|
201
|
+
but *only* if the verification phase was actually performed
|
|
202
|
+
and succeeded -- hence *never* for a plan whose
|
|
203
|
+
`## VERIFICATION` section is deliberately omitted.
|
|
204
|
+
|
|
205
|
+
- Set the `Status:` key to `COMPLETED`, but *only* if the
|
|
206
|
+
change set was applied *completely* and *successfully* --
|
|
207
|
+
this traverses the `start` and `complete` transitions of the
|
|
208
|
+
state machine of the plan <format/> in one go. Otherwise
|
|
209
|
+
leave the `Status:` key *untouched*, as an incomplete run
|
|
210
|
+
transitioned nowhere.
|
|
211
|
+
|
|
212
|
+
- Refresh the `Modified:` key with the current time in
|
|
213
|
+
ISO-style format, determined by calling the
|
|
214
|
+
`ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase`
|
|
215
|
+
MCP server.
|
|
216
|
+
|
|
217
|
+
Then call the `ase_task_save(id: "<ase-task-id/>", text:
|
|
218
|
+
"<task-content/>")` tool of the `ase` MCP server to persist the
|
|
219
|
+
updated task plan. Do not output anything in this sub-step.
|
|
220
|
+
|
|
221
|
+
3. Only output the following <template/>:
|
|
193
222
|
|
|
194
223
|
<template>
|
|
195
224
|
⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan implemented**
|
|
196
225
|
</template>
|
|
197
226
|
|
|
198
|
-
|
|
227
|
+
4. <if condition="<worktree-dir/> is not empty">
|
|
199
228
|
Give the closing hint by expanding the following (which,
|
|
200
229
|
depending on the configured <ase-guidance-level/>, may expand
|
|
201
230
|
into nothing and hence emit no output at all):
|
|
@@ -17,13 +17,17 @@ The `ase-task-implement` skill performs the *final implementation* of
|
|
|
17
17
|
a task plan by modifying the corresponding *artifacts* with a complete
|
|
18
18
|
*change set*. The plan is loaded and any optional `IMPLEMENTATION DRAFT`
|
|
19
19
|
section produced by `ase-task-preflight` is used as a hint - the plain
|
|
20
|
-
plan content always overrules the draft.
|
|
20
|
+
plan content always overrules the draft. Afterwards the plan's
|
|
21
|
+
`Properties:` frontmatter key gains the value `implemented` (plus
|
|
22
|
+
`verified`, if the verification phase actually ran and succeeded), and
|
|
23
|
+
its `Status:` key becomes `COMPLETED` if the change set was applied
|
|
24
|
+
completely and successfully.
|
|
21
25
|
|
|
22
|
-
The *kind of change* stated by the plan's
|
|
26
|
+
The *kind of change* stated by the plan's `Kind:` frontmatter key
|
|
23
27
|
(`CRAFTING`, `REFACTORING`, or `RESOLVING`) selects which
|
|
24
28
|
*operation-specific tenet set* of the **ASE Tenets** is internalized
|
|
25
29
|
before any artifact is touched, in addition to the always applying
|
|
26
|
-
**GENERIC TENETS**. If a plan carries no such
|
|
30
|
+
**GENERIC TENETS**. If a plan carries no such key, the kind is
|
|
27
31
|
*inferred* from the plan content, defaulting to `CRAFTING`.
|
|
28
32
|
|
|
29
33
|
If the task plan deliberately *omits* the `## VERIFICATION` section
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ase-task-list
|
|
3
|
-
argument-hint: "[--help|-h] [--verbose|-v]"
|
|
3
|
+
argument-hint: "[--help|-h] [--verbose|-v] [--include|-i=<state>[,...]] [--exclude|-e=<state>[,...]]"
|
|
4
4
|
description: >
|
|
5
5
|
List all available task ids.
|
|
6
6
|
Use when user wants to see all tasks.
|
|
@@ -19,7 +19,7 @@ List Task Plans
|
|
|
19
19
|
|
|
20
20
|
<expand name="getopt"
|
|
21
21
|
arg1="ase-task-list"
|
|
22
|
-
arg2="--verbose|-v">
|
|
22
|
+
arg2="--verbose|-v --include|-i=none --exclude|-e=COMPLETED,CANCELLED">
|
|
23
23
|
$ARGUMENTS
|
|
24
24
|
</expand>
|
|
25
25
|
|
|
@@ -30,13 +30,40 @@ List Task Plans
|
|
|
30
30
|
Procedure
|
|
31
31
|
---------
|
|
32
32
|
|
|
33
|
-
1.
|
|
33
|
+
1. Determine the *effective state set* <states/>, i.e., the lifecycle
|
|
34
|
+
states a task plan has to be in to be listed at all. For this, parse
|
|
35
|
+
<getopt-option-include/> and <getopt-option-exclude/> as
|
|
36
|
+
comma-separated token lists, silently dropping the `none` sentinel
|
|
37
|
+
and any empty token. If a token <token/> is *not* one of the eight
|
|
38
|
+
`Status:` values `DRAFTED`, `REJECTED`, `APPROVED`, `DEFERRED`,
|
|
39
|
+
`STARTED`, `BLOCKED`, `COMPLETED`, or `CANCELLED`, only output the
|
|
40
|
+
following <template/> and then *STOP* processing the entire current
|
|
41
|
+
skill:
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
⧉ **ASE**: ✪ skill: **ase-task-list**, ▶ ERROR: invalid state: **<token/>**
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
Otherwise set <states/> to *all* eight states if both lists are
|
|
48
|
+
empty, to the *include* list if only it is non-empty, to all eight
|
|
49
|
+
*minus* the *exclude* list if only it is non-empty, and to the
|
|
50
|
+
*include* list *minus* the *exclude* list if both are non-empty. If
|
|
51
|
+
the resulting <states/> is *empty*, only output the following
|
|
52
|
+
<template/> and then *STOP* processing the entire current skill:
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
⧉ **ASE**: ✪ skill: **ase-task-list**, ▶ ERROR: options `--include` and `--exclude` cancel out to an empty state set
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
2. Call the `ase_task_list(verbose: <getopt-option-verbose/>)` tool from
|
|
34
59
|
the `ase` MCP server. The result is a structured object with a
|
|
35
|
-
`tasks` array where each entry has an `id` field, and
|
|
36
|
-
<getopt-option-verbose/> is `true` -- additionally an `mtime`
|
|
37
|
-
(formatted as `YYYY-MM-DD HH:MM`).
|
|
60
|
+
`tasks` array where each entry has an `id` and a `status` field, and
|
|
61
|
+
-- if <getopt-option-verbose/> is `true` -- additionally an `mtime`
|
|
62
|
+
field (formatted as `YYYY-MM-DD HH:MM`). *Drop* from the `tasks`
|
|
63
|
+
array every entry whose `status` is *not* contained in <states/>. Do
|
|
64
|
+
not output anything.
|
|
38
65
|
|
|
39
|
-
|
|
66
|
+
3. If the `tasks` array is empty, output the following <template/>:
|
|
40
67
|
|
|
41
68
|
<template>
|
|
42
69
|
⧉ **ASE**: ◉ tasks: *(none)*
|
|
@@ -45,16 +72,16 @@ Procedure
|
|
|
45
72
|
Else, dispatch on <getopt-option-verbose/>:
|
|
46
73
|
|
|
47
74
|
- If <getopt-option-verbose/> is `true`, output the list of tasks
|
|
48
|
-
with the following <template/>, where each <id
|
|
49
|
-
correspond to an entry in the task list:
|
|
75
|
+
with the following <template/>, where each <id/>, <status/>, and
|
|
76
|
+
<mtime/> correspond to an entry in the task list:
|
|
50
77
|
|
|
51
78
|
<template>
|
|
52
79
|
⧉ **ASE**: ◉ tasks:
|
|
53
80
|
|
|
54
|
-
| *Task Id* | *Last Modified* |
|
|
55
|
-
|
|
56
|
-
| **<id/>** | `<mtime/>` |
|
|
57
|
-
| [...] | [...] |
|
|
81
|
+
| *Task Id* | *Status* | *Last Modified* |
|
|
82
|
+
|-----------|-------------|--------------------|
|
|
83
|
+
| **<id/>** | `<status/>` | `<mtime/>` |
|
|
84
|
+
| [...] | [...] | [...] |
|
|
58
85
|
|
|
59
86
|
</template>
|
|
60
87
|
|
|
@@ -72,7 +99,7 @@ Procedure
|
|
|
72
99
|
|
|
73
100
|
</template>
|
|
74
101
|
|
|
75
|
-
|
|
102
|
+
4. Finally, give the closing hints by expanding the following (which,
|
|
76
103
|
depending on the configured <ase-guidance-level/>, may each expand
|
|
77
104
|
into nothing and hence emit no output at all):
|
|
78
105
|
|
|
@@ -81,6 +108,11 @@ Procedure
|
|
|
81
108
|
Use `/ase-task-id <id>` to switch to one of the listed tasks and `/ase-task-view` to inspect its plan.
|
|
82
109
|
</ase-tpl-hint>
|
|
83
110
|
</if>
|
|
111
|
+
<elseif condition="entries were dropped by the state filtering of step 2">
|
|
112
|
+
<ase-tpl-hint level="normal">
|
|
113
|
+
All task plans were filtered out by the effective state set -- use `/ase-task-list --exclude none` to list them regardless of their status.
|
|
114
|
+
</ase-tpl-hint>
|
|
115
|
+
</elseif>
|
|
84
116
|
<else>
|
|
85
117
|
<ase-tpl-hint level="normal">
|
|
86
118
|
No task plan exists yet -- use `/ase-task-edit` to create one through a conversational loop.
|
|
@@ -89,7 +121,11 @@ Procedure
|
|
|
89
121
|
|
|
90
122
|
<if condition="<getopt-option-verbose/> is not equal `true`">
|
|
91
123
|
<ase-tpl-hint level="verbose">
|
|
92
|
-
Use `/ase-task-list --verbose` to additionally show the last-modified timestamp of each task plan.
|
|
124
|
+
Use `/ase-task-list --verbose` to additionally show the status and the last-modified timestamp of each task plan.
|
|
93
125
|
</ase-tpl-hint>
|
|
94
126
|
</if>
|
|
95
127
|
|
|
128
|
+
<ase-tpl-hint level="verbose">
|
|
129
|
+
Use `/ase-task-list --include`/`--exclude` to narrow the listing to certain lifecycle states, e.g. `--include STARTED,BLOCKED`.
|
|
130
|
+
</ase-tpl-hint>
|
|
131
|
+
|
|
@@ -8,35 +8,78 @@
|
|
|
8
8
|
`ase-task-list`
|
|
9
9
|
[`--help`|`-h`]
|
|
10
10
|
[`--verbose`|`-v`]
|
|
11
|
+
[`--include`|`-i`=*state*[`,`...]]
|
|
12
|
+
[`--exclude`|`-e`=*state*[`,`...]]
|
|
11
13
|
|
|
12
14
|
## DESCRIPTION
|
|
13
15
|
|
|
14
16
|
The `ase-task-list` skill lists all available *task ids* in the
|
|
15
17
|
current project by calling the `ase_task_list` MCP tool. In the
|
|
16
18
|
default mode, only the task ids are rendered as a single-column
|
|
17
|
-
Markdown table. In verbose mode, the
|
|
18
|
-
each task plan
|
|
19
|
+
Markdown table. In verbose mode, the lifecycle status and the
|
|
20
|
+
last-modified timestamp of each task plan are rendered as additional
|
|
21
|
+
columns.
|
|
22
|
+
|
|
23
|
+
The listing is restricted to an *effective state set*, derived from the
|
|
24
|
+
`Status:` frontmatter key of each task plan (which defaults to `DRAFTED`
|
|
25
|
+
for a plan carrying no such key): with `--include` only, exactly the
|
|
26
|
+
listed states are shown; with `--exclude` only, all states except the
|
|
27
|
+
listed ones; with both, the included ones minus the excluded ones. An
|
|
28
|
+
unknown state, or a combination which cancels out to an empty set,
|
|
29
|
+
aborts the skill with an error. By default,
|
|
30
|
+
`--exclude COMPLETED,CANCELLED` is in effect, so finished and
|
|
31
|
+
abandoned task plans stay out of the way. The eight states are:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
DRAFTED APPROVED STARTED COMPLETED
|
|
35
|
+
REJECTED DEFERRED BLOCKED CANCELLED
|
|
36
|
+
```
|
|
19
37
|
|
|
20
38
|
## OPTIONS
|
|
21
39
|
|
|
22
40
|
`--verbose`|`-v`:
|
|
23
|
-
Render an additional `
|
|
24
|
-
`YYYY-MM-DD HH:MM`
|
|
41
|
+
Render an additional `Status` column with the lifecycle state and an
|
|
42
|
+
additional `Last Modified` column with the `YYYY-MM-DD HH:MM`
|
|
43
|
+
timestamp of each task plan.
|
|
44
|
+
|
|
45
|
+
`--include`|`-i`=*state*[`,`...]:
|
|
46
|
+
Restrict the listed task plans to the given comma-separated list of
|
|
47
|
+
lifecycle states (e.g. `STARTED,BLOCKED`). Without this option, all
|
|
48
|
+
eight states are listed. The `none` sentinel selects no state at all.
|
|
49
|
+
|
|
50
|
+
`--exclude`|`-e`=*state*[`,`...]:
|
|
51
|
+
Remove the given comma-separated list of lifecycle states from the
|
|
52
|
+
listed task plans. Applied *after* `--include`, so
|
|
53
|
+
`-i DRAFTED,STARTED -e STARTED` lists `DRAFTED` only. Defaults to
|
|
54
|
+
`COMPLETED,CANCELLED`; pass `--exclude none` to suppress the
|
|
55
|
+
default and list task plans in every state.
|
|
25
56
|
|
|
26
57
|
## EXAMPLES
|
|
27
58
|
|
|
28
|
-
List all task ids:
|
|
59
|
+
List all unfinished task ids:
|
|
29
60
|
|
|
30
61
|
```text
|
|
31
62
|
❯ /ase-task-list
|
|
32
63
|
```
|
|
33
64
|
|
|
34
|
-
List all task ids together with their last-modified timestamps:
|
|
65
|
+
List all task ids together with their status and last-modified timestamps:
|
|
35
66
|
|
|
36
67
|
```text
|
|
37
68
|
❯ /ase-task-list --verbose
|
|
38
69
|
```
|
|
39
70
|
|
|
71
|
+
List the task ids of every task plan, including the finished ones:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
❯ /ase-task-list --exclude none
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
List only the task ids of the task plans currently under work:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
❯ /ase-task-list --include STARTED,BLOCKED
|
|
81
|
+
```
|
|
82
|
+
|
|
40
83
|
## SEE ALSO
|
|
41
84
|
|
|
42
85
|
[`ase-task-id`](../ase-task-id/help.md), [`ase-task-view`](../ase-task-view/help.md), [`ase-task-edit`](../ase-task-edit/help.md),
|
|
@@ -88,16 +88,22 @@ Procedure
|
|
|
88
88
|
|
|
89
89
|
</template>
|
|
90
90
|
|
|
91
|
-
3. <if condition="<task-content/>
|
|
91
|
+
3. <if condition="the frontmatter of <task-content/> carries a `Modified:` key">
|
|
92
92
|
Update <timestamp-modified/> with the current time in
|
|
93
93
|
ISO-style format, which has to be determined by calling the
|
|
94
94
|
`ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase`
|
|
95
95
|
MCP server and use the `text` field of its response. Update
|
|
96
|
-
the
|
|
97
|
-
|
|
96
|
+
the `Modified: ...` frontmatter key of <task-content/> with the
|
|
97
|
+
new <timestamp-modified/> value.
|
|
98
98
|
Do not output anything.
|
|
99
99
|
</if>
|
|
100
100
|
|
|
101
|
+
Additionally *add* the value `preflighted` to the `Properties:`
|
|
102
|
+
frontmatter key of <task-content/> if it is still absent, keeping
|
|
103
|
+
all already present values and *creating* the whole key (with the
|
|
104
|
+
single value `preflighted`) if the plan carries none. Do not
|
|
105
|
+
output anything.
|
|
106
|
+
|
|
101
107
|
4. Finally, call the `ase_task_save(id: "<ase-task-id/>",
|
|
102
108
|
text: "<task-content/>")` tool of the `ase` MCP server to save the updated
|
|
103
109
|
task plan content. Calculate the number of words <words/> of
|
|
@@ -16,14 +16,15 @@ The `ase-task-preflight` skill performs a *preflight* (dry-run,
|
|
|
16
16
|
test-drive) of the *implementation* of a task plan by creating a
|
|
17
17
|
draft for a corresponding, complete *artifact change set* in
|
|
18
18
|
*unified diff* format. The draft is appended to the task plan as
|
|
19
|
-
an `IMPLEMENTATION DRAFT` section (replacing any previous draft)
|
|
19
|
+
an `IMPLEMENTATION DRAFT` section (replacing any previous draft) and
|
|
20
|
+
the plan's `Properties:` frontmatter key gains the value `preflighted`.
|
|
20
21
|
No source files are modified.
|
|
21
22
|
|
|
22
|
-
The *kind of change* stated by the plan's
|
|
23
|
+
The *kind of change* stated by the plan's `Kind:` frontmatter key
|
|
23
24
|
(`CRAFTING`, `REFACTORING`, or `RESOLVING`) selects which
|
|
24
25
|
*operation-specific tenet set* of the **ASE Tenets** is internalized
|
|
25
26
|
before the draft is produced, in addition to the always applying
|
|
26
|
-
**GENERIC TENETS**. If a plan carries no such
|
|
27
|
+
**GENERIC TENETS**. If a plan carries no such key, the kind is
|
|
27
28
|
*inferred* from the plan content, defaulting to `CRAFTING`.
|
|
28
29
|
|
|
29
30
|
After the preflight, the user is asked whether to stop, hand
|
|
@@ -126,7 +126,7 @@ Procedure
|
|
|
126
126
|
(append extracted text to instruction).
|
|
127
127
|
</if>
|
|
128
128
|
|
|
129
|
-
6. <if condition="<task-content/>
|
|
129
|
+
6. <if condition="the frontmatter of <task-content/> carries a `Created: <text/>` key">
|
|
130
130
|
Set <timestamp-created><text/></timestamp-created> (set
|
|
131
131
|
timestamp-created to extracted text)
|
|
132
132
|
</if>
|
|
@@ -78,6 +78,27 @@ Procedure
|
|
|
78
78
|
|
|
79
79
|
2. <if condition="<task-content/> is not empty">
|
|
80
80
|
Treat <task-content/> as *verbatim* Markdown.
|
|
81
|
+
|
|
82
|
+
For the *rendering only*, drop the leading *frontmatter* block --
|
|
83
|
+
both `---` delimiters and all of their keys -- and instead place
|
|
84
|
+
the following column-aligned glyph lines *before* the
|
|
85
|
+
`# TASK: <title/>` heading, separated from it by an empty line,
|
|
86
|
+
omitting the line of every key absent from the frontmatter. The
|
|
87
|
+
glyph lines *MUST* stay *above* the heading, exactly where the
|
|
88
|
+
frontmatter block sits in the plan file, and *MUST NOT* be moved
|
|
89
|
+
below it. This keeps the `---` delimiters from rendering as a
|
|
90
|
+
horizontal rule plus a *setext heading*. This rewrite is
|
|
91
|
+
*display-only* and *MUST NOT* change <task-content/> itself:
|
|
92
|
+
|
|
93
|
+
<format>
|
|
94
|
+
◉ **Id:** <task-id/>
|
|
95
|
+
⎈ **Created:** <timestamp-created/>
|
|
96
|
+
⚙ **Modified:** <timestamp-modified/>
|
|
97
|
+
◐ **Status:** <task-status/>
|
|
98
|
+
⚑ **Properties:** <task-properties/>
|
|
99
|
+
☯ **Kind:** <task-kind/>
|
|
100
|
+
</format>
|
|
101
|
+
|
|
81
102
|
*Render plan*: Only output the following <template/>. If
|
|
82
103
|
<getopt-option-full/> is *not* `true`, <task-content/> is longer than
|
|
83
104
|
90 lines, and a `## IMPLEMENTATION DRAFT` section (from the
|
|
@@ -87,9 +108,9 @@ Procedure
|
|
|
87
108
|
Use the following <template/>:
|
|
88
109
|
|
|
89
110
|
<template>
|
|
90
|
-
<ase-tpl-head title="TASK"/>
|
|
111
|
+
<ase-tpl-head title="TASK" subtitle="<task-id/>"/>
|
|
91
112
|
<task-content/>
|
|
92
|
-
<ase-tpl-foot title="TASK"/>
|
|
113
|
+
<ase-tpl-foot title="TASK" subtitle="<task-id/>"/>
|
|
93
114
|
</template>
|
|
94
115
|
</if>
|
|
95
116
|
|