@rse/ase 0.9.4 → 0.9.6
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-artifact.js +331 -0
- package/dst/ase-config.js +56 -10
- package/dst/ase-service.js +2 -0
- package/dst/ase-task.js +139 -64
- package/dst/ase.js +2 -0
- package/package.json +5 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/meta/ase-format-meta.md +5 -2
- package/plugin/meta/{ase-format-plan.md → ase-format-task.md} +7 -6
- package/plugin/package.json +1 -1
- package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
- package/plugin/skills/ase-arch-discover/SKILL.md +17 -9
- package/plugin/skills/ase-arch-discover/help.md +14 -0
- package/plugin/skills/ase-code-craft/SKILL.md +2 -2
- package/plugin/skills/ase-code-insight/SKILL.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +1 -1
- package/plugin/skills/ase-code-refactor/SKILL.md +2 -2
- package/plugin/skills/ase-code-resolve/SKILL.md +3 -3
- package/plugin/skills/ase-docs-proofread/SKILL.md +1 -1
- package/plugin/skills/ase-meta-brainstorm/SKILL.md +22 -21
- package/plugin/skills/ase-meta-brainstorm/help.md +38 -12
- package/plugin/skills/ase-meta-chat/SKILL.md +1 -1
- package/plugin/skills/ase-meta-diaboli/SKILL.md +1 -1
- package/plugin/skills/ase-meta-diff/SKILL.md +1 -1
- package/plugin/skills/ase-meta-quorum/SKILL.md +37 -5
- package/plugin/skills/ase-meta-quorum/help.md +18 -0
- package/plugin/skills/ase-meta-review/SKILL.md +1 -1
- package/plugin/skills/ase-meta-search/SKILL.md +35 -6
- package/plugin/skills/ase-meta-search/help.md +14 -2
- package/plugin/skills/ase-meta-steelman/SKILL.md +1 -1
- package/plugin/skills/ase-meta-why/SKILL.md +117 -25
- package/plugin/skills/ase-meta-why/help.md +30 -2
- package/plugin/skills/ase-task-condense/SKILL.md +1 -1
- package/plugin/skills/ase-task-delete/help.md +0 -4
- package/plugin/skills/ase-task-edit/SKILL.md +4 -4
- package/plugin/skills/ase-task-edit/help.md +0 -4
- package/plugin/skills/ase-task-grill/SKILL.md +1 -1
- package/plugin/skills/ase-task-grill/help.md +0 -4
- package/plugin/skills/ase-task-implement/SKILL.md +1 -1
- package/plugin/skills/ase-task-implement/help.md +5 -6
- package/plugin/skills/ase-task-preflight/SKILL.md +1 -1
- package/plugin/skills/ase-task-reboot/SKILL.md +1 -1
package/dst/ase-task.js
CHANGED
|
@@ -7,11 +7,13 @@ import path from "node:path";
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import { execaSync } from "execa";
|
|
9
9
|
import { DateTime } from "luxon";
|
|
10
|
+
import picomatch from "picomatch";
|
|
10
11
|
import { isScalar } from "yaml";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import { Config, configSchema, parseScope } from "./ase-config.js";
|
|
13
14
|
/* reusable functionality: persisted task plans under
|
|
14
|
-
<project
|
|
15
|
+
<project>/<basedir>/TASK-<id>.md (driven by the
|
|
16
|
+
"project.artifact.task.{basedir,files}" configuration) */
|
|
15
17
|
export class Task {
|
|
16
18
|
/* validate the task id to keep it safe as a filename component */
|
|
17
19
|
static validateId(id) {
|
|
@@ -34,100 +36,173 @@ export class Task {
|
|
|
34
36
|
}
|
|
35
37
|
return process.cwd();
|
|
36
38
|
}
|
|
39
|
+
/* read the configured "basedir" anchor and "files" miniglob spec for
|
|
40
|
+
task storage; "basedir" is project-root-relative (POSIX, defaults
|
|
41
|
+
to ".ase/task") and "files" constrains the task filenames
|
|
42
|
+
(defaults to "*.md") */
|
|
43
|
+
static spec(log) {
|
|
44
|
+
const cfg = new Config("config", configSchema, log);
|
|
45
|
+
cfg.read();
|
|
46
|
+
const read = (key) => {
|
|
47
|
+
const val = cfg.get(key);
|
|
48
|
+
if (val === undefined)
|
|
49
|
+
return "";
|
|
50
|
+
return String(isScalar(val) ? val.value : val);
|
|
51
|
+
};
|
|
52
|
+
const basedir = (read("project.artifact.task.basedir") || ".ase/task")
|
|
53
|
+
.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
54
|
+
const files = read("project.artifact.task.files") || "*.md";
|
|
55
|
+
return { basedir, files };
|
|
56
|
+
}
|
|
37
57
|
/* resolve the on-disk base directory for task storage */
|
|
38
|
-
static baseDir() {
|
|
39
|
-
return path.join(Task.projectRoot(),
|
|
58
|
+
static baseDir(log) {
|
|
59
|
+
return path.join(Task.projectRoot(), Task.spec(log).basedir);
|
|
40
60
|
}
|
|
41
|
-
/* resolve the on-disk path for a given task id
|
|
42
|
-
|
|
61
|
+
/* resolve the on-disk path for a given task id; as a side effect,
|
|
62
|
+
eagerly migrate any legacy <basedir>/<id>/plan.md files to the
|
|
63
|
+
current <basedir>/TASK-<id>.md layout on first access (guarded by
|
|
64
|
+
a cheap check, so it is a no-op once the store is migrated) */
|
|
65
|
+
static path(log, id) {
|
|
43
66
|
Task.validateId(id);
|
|
44
|
-
|
|
67
|
+
if (Task.needsMigration(log))
|
|
68
|
+
Task.migrateAll(log);
|
|
69
|
+
return path.join(Task.baseDir(log), `TASK-${id}.md`);
|
|
70
|
+
}
|
|
71
|
+
/* cheaply check whether any legacy <basedir>/<id>/plan.md file still
|
|
72
|
+
exists in the task base directory and thus needs migration */
|
|
73
|
+
static needsMigration(log) {
|
|
74
|
+
const dir = Task.baseDir(log);
|
|
75
|
+
if (!fs.existsSync(dir))
|
|
76
|
+
return false;
|
|
77
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
78
|
+
if (!entry.isDirectory() || !/^[A-Za-z0-9-]+$/.test(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
if (fs.existsSync(path.join(dir, entry.name, "plan.md")))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/* migrate all legacy <basedir>/<id>/plan.md task files to the current
|
|
86
|
+
<basedir>/TASK-<id>.md layout; an existing TASK-<id>.md is never
|
|
87
|
+
overwritten; the now-empty <id>/ directory is removed afterwards;
|
|
88
|
+
returns the list of migrated task ids in lexicographic order */
|
|
89
|
+
static migrateAll(log) {
|
|
90
|
+
const dir = Task.baseDir(log);
|
|
91
|
+
if (!fs.existsSync(dir))
|
|
92
|
+
return [];
|
|
93
|
+
const migrated = [];
|
|
94
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
95
|
+
if (!entry.isDirectory() || !/^[A-Za-z0-9-]+$/.test(entry.name))
|
|
96
|
+
continue;
|
|
97
|
+
const id = entry.name;
|
|
98
|
+
const oldFile = path.join(dir, id, "plan.md");
|
|
99
|
+
const newFile = path.join(dir, `TASK-${id}.md`);
|
|
100
|
+
if (!fs.existsSync(oldFile))
|
|
101
|
+
continue;
|
|
102
|
+
if (fs.existsSync(newFile)) {
|
|
103
|
+
log.write("warning", `task: not migrating "${id}": target "TASK-${id}.md" already exists`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
fs.renameSync(oldFile, newFile);
|
|
107
|
+
fs.rmSync(path.join(dir, id), { recursive: true, force: true });
|
|
108
|
+
migrated.push(id);
|
|
109
|
+
}
|
|
110
|
+
migrated.sort((a, b) => a.localeCompare(b));
|
|
111
|
+
return migrated;
|
|
45
112
|
}
|
|
46
113
|
/* load a task; returns empty string if no task exists */
|
|
47
|
-
static load(id) {
|
|
48
|
-
const file = Task.path(id);
|
|
114
|
+
static load(log, id) {
|
|
115
|
+
const file = Task.path(log, id);
|
|
49
116
|
if (!fs.existsSync(file))
|
|
50
117
|
return "";
|
|
51
118
|
return fs.readFileSync(file, "utf8");
|
|
52
119
|
}
|
|
53
|
-
/* save a task as UTF-8 text under the given id
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
static save(id, text) {
|
|
120
|
+
/* save a task as UTF-8 text under the given id into the
|
|
121
|
+
<project>/<basedir>/TASK-<id>.md file */
|
|
122
|
+
static save(log, id, text) {
|
|
57
123
|
if (typeof text !== "string")
|
|
58
124
|
throw new Error("task: text must be a string");
|
|
59
|
-
const file = Task.path(id);
|
|
125
|
+
const file = Task.path(log, id);
|
|
60
126
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
61
127
|
fs.writeFileSync(file, text, "utf8");
|
|
62
128
|
}
|
|
63
|
-
/* delete a task by id; removes the
|
|
64
|
-
<project
|
|
65
|
-
static delete(id) {
|
|
66
|
-
const file = Task.path(id);
|
|
129
|
+
/* delete a task by id; removes the single
|
|
130
|
+
<project>/<basedir>/TASK-<id>.md file; returns true if a task existed */
|
|
131
|
+
static delete(log, id) {
|
|
132
|
+
const file = Task.path(log, id);
|
|
67
133
|
if (!fs.existsSync(file))
|
|
68
134
|
return false;
|
|
69
|
-
fs.rmSync(
|
|
135
|
+
fs.rmSync(file, { force: true });
|
|
70
136
|
return true;
|
|
71
137
|
}
|
|
72
|
-
/* rename a task by moving
|
|
73
|
-
<project
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
138
|
+
/* rename a task by moving its <project>/<basedir>/TASK-<oldId>.md file
|
|
139
|
+
to <project>/<basedir>/TASK-<newId>.md; the embedded
|
|
140
|
+
"# ✪ TASK <id>:" heading inside the plan content is rewritten to
|
|
141
|
+
the new id; returns true on success, false if the source task does
|
|
142
|
+
not exist; throws if the target id already exists */
|
|
143
|
+
static rename(log, oldId, newId) {
|
|
144
|
+
const oldFile = Task.path(log, oldId);
|
|
145
|
+
const newFile = Task.path(log, newId);
|
|
146
|
+
if (!fs.existsSync(oldFile))
|
|
80
147
|
return false;
|
|
81
|
-
if (fs.existsSync(
|
|
148
|
+
if (fs.existsSync(newFile))
|
|
82
149
|
throw new Error(`task: target id "${newId}" already exists`);
|
|
83
|
-
fs.
|
|
84
|
-
|
|
150
|
+
const text = fs.readFileSync(oldFile, "utf8");
|
|
151
|
+
const updated = text.replace(/(^#\s+(?:✪\s+)?TASK\s+)[A-Za-z0-9-]+(\s*:)/m, `$1${newId}$2`);
|
|
152
|
+
fs.mkdirSync(path.dirname(newFile), { recursive: true });
|
|
153
|
+
fs.writeFileSync(newFile, updated, "utf8");
|
|
154
|
+
fs.rmSync(oldFile, { force: true });
|
|
85
155
|
return true;
|
|
86
156
|
}
|
|
87
157
|
/* list all persisted tasks in lexicographic id order; if verbose is true,
|
|
88
|
-
each entry's `mtime` is set to the
|
|
89
|
-
as "YYYY-MM-DD HH:MM", otherwise it is left undefined */
|
|
90
|
-
static list(verbose = false) {
|
|
91
|
-
|
|
158
|
+
each entry's `mtime` is set to the task file's modification time
|
|
159
|
+
formatted as "YYYY-MM-DD HH:MM", otherwise it is left undefined */
|
|
160
|
+
static list(log, verbose = false) {
|
|
161
|
+
if (Task.needsMigration(log))
|
|
162
|
+
Task.migrateAll(log);
|
|
163
|
+
const { basedir, files } = Task.spec(log);
|
|
164
|
+
const dir = path.join(Task.projectRoot(), basedir);
|
|
92
165
|
if (!fs.existsSync(dir))
|
|
93
166
|
return [];
|
|
167
|
+
const isMatch = picomatch(files, { dot: true });
|
|
94
168
|
const out = [];
|
|
95
169
|
for (const entry of fs.readdirSync(dir)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const file = path.join(dir, entry, "plan.md");
|
|
99
|
-
if (!fs.existsSync(file))
|
|
170
|
+
const m = /^TASK-([A-Za-z0-9-]+)\.md$/.exec(entry);
|
|
171
|
+
if (m === null || !isMatch(entry))
|
|
100
172
|
continue;
|
|
173
|
+
const file = path.join(dir, entry);
|
|
101
174
|
const st = fs.statSync(file);
|
|
102
175
|
if (!st.isFile())
|
|
103
176
|
continue;
|
|
104
177
|
const mtime = verbose ? DateTime.fromJSDate(st.mtime).toFormat("yyyy-LL-dd HH:mm") : undefined;
|
|
105
|
-
out.push({ id:
|
|
178
|
+
out.push({ id: m[1], mtime });
|
|
106
179
|
}
|
|
107
180
|
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
108
181
|
return out;
|
|
109
182
|
}
|
|
110
183
|
/* purge tasks whose modification time is older than the given cutoff in
|
|
111
184
|
milliseconds; returns the list of removed task ids */
|
|
112
|
-
static purge(maxAgeMs) {
|
|
113
|
-
|
|
185
|
+
static purge(log, maxAgeMs) {
|
|
186
|
+
if (Task.needsMigration(log))
|
|
187
|
+
Task.migrateAll(log);
|
|
188
|
+
const { basedir, files } = Task.spec(log);
|
|
189
|
+
const dir = path.join(Task.projectRoot(), basedir);
|
|
114
190
|
if (!fs.existsSync(dir))
|
|
115
191
|
return [];
|
|
192
|
+
const isMatch = picomatch(files, { dot: true });
|
|
116
193
|
const cutoff = Date.now() - maxAgeMs;
|
|
117
194
|
const removed = [];
|
|
118
195
|
for (const entry of fs.readdirSync(dir)) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const sub = path.join(dir, entry);
|
|
122
|
-
const file = path.join(sub, "plan.md");
|
|
123
|
-
if (!fs.existsSync(file))
|
|
196
|
+
const m = /^TASK-([A-Za-z0-9-]+)\.md$/.exec(entry);
|
|
197
|
+
if (m === null || !isMatch(entry))
|
|
124
198
|
continue;
|
|
199
|
+
const file = path.join(dir, entry);
|
|
125
200
|
const st = fs.statSync(file);
|
|
126
201
|
if (!st.isFile())
|
|
127
202
|
continue;
|
|
128
203
|
if (st.mtimeMs < cutoff) {
|
|
129
|
-
fs.rmSync(
|
|
130
|
-
removed.push(
|
|
204
|
+
fs.rmSync(file, { force: true });
|
|
205
|
+
removed.push(m[1]);
|
|
131
206
|
}
|
|
132
207
|
}
|
|
133
208
|
return removed;
|
|
@@ -173,7 +248,7 @@ export default class TaskCommand {
|
|
|
173
248
|
/* register CLI top-level command "ase task" */
|
|
174
249
|
const task = program
|
|
175
250
|
.command("task")
|
|
176
|
-
.description("Manage persisted tasks under <project
|
|
251
|
+
.description("Manage persisted tasks under <project>/<basedir>/TASK-<id>.md")
|
|
177
252
|
.action(() => {
|
|
178
253
|
task.outputHelp();
|
|
179
254
|
process.exit(1);
|
|
@@ -182,9 +257,9 @@ export default class TaskCommand {
|
|
|
182
257
|
task
|
|
183
258
|
.command("list")
|
|
184
259
|
.description("List all persisted task ids, one per line")
|
|
185
|
-
.option("-v, --verbose", "also show the
|
|
260
|
+
.option("-v, --verbose", "also show the task file modification time as (YYYY-MM-DD HH:MM)")
|
|
186
261
|
.action((opts) => {
|
|
187
|
-
const items = Task.list(opts.verbose ?? false);
|
|
262
|
+
const items = Task.list(this.log, opts.verbose ?? false);
|
|
188
263
|
for (const item of items) {
|
|
189
264
|
if (opts.verbose)
|
|
190
265
|
process.stdout.write(`${item.id}\t(${item.mtime})\n`);
|
|
@@ -199,7 +274,7 @@ export default class TaskCommand {
|
|
|
199
274
|
.description("Load a task by id and write it to stdout")
|
|
200
275
|
.argument("<id>", "Task identifier")
|
|
201
276
|
.action((id) => {
|
|
202
|
-
const text = Task.load(id);
|
|
277
|
+
const text = Task.load(this.log, id);
|
|
203
278
|
process.stdout.write(text);
|
|
204
279
|
process.exit(0);
|
|
205
280
|
});
|
|
@@ -209,7 +284,7 @@ export default class TaskCommand {
|
|
|
209
284
|
.description("Edit a task by id with $EDITOR")
|
|
210
285
|
.argument("<id>", "Task identifier")
|
|
211
286
|
.action((id) => {
|
|
212
|
-
const file = Task.path(id);
|
|
287
|
+
const file = Task.path(this.log, id);
|
|
213
288
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
214
289
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
215
290
|
if (!fs.existsSync(file))
|
|
@@ -225,7 +300,7 @@ export default class TaskCommand {
|
|
|
225
300
|
.argument("<id>", "Task identifier")
|
|
226
301
|
.action(async (id) => {
|
|
227
302
|
const text = await readStdin();
|
|
228
|
-
Task.save(id, text);
|
|
303
|
+
Task.save(this.log, id, text);
|
|
229
304
|
this.log.write("info", `task: saved "${id}"`);
|
|
230
305
|
process.exit(0);
|
|
231
306
|
});
|
|
@@ -235,7 +310,7 @@ export default class TaskCommand {
|
|
|
235
310
|
.description("Delete a task by id")
|
|
236
311
|
.argument("<id>", "Task identifier")
|
|
237
312
|
.action((id) => {
|
|
238
|
-
const removed = Task.delete(id);
|
|
313
|
+
const removed = Task.delete(this.log, id);
|
|
239
314
|
if (removed)
|
|
240
315
|
this.log.write("info", `task: removed "${id}"`);
|
|
241
316
|
else
|
|
@@ -249,7 +324,7 @@ export default class TaskCommand {
|
|
|
249
324
|
.argument("<old>", "Old task identifier")
|
|
250
325
|
.argument("<new>", "New task identifier")
|
|
251
326
|
.action((oldId, newId) => {
|
|
252
|
-
const renamed = Task.rename(oldId, newId);
|
|
327
|
+
const renamed = Task.rename(this.log, oldId, newId);
|
|
253
328
|
if (renamed)
|
|
254
329
|
this.log.write("info", `task: renamed "${oldId}" to "${newId}"`);
|
|
255
330
|
else
|
|
@@ -276,7 +351,7 @@ export default class TaskCommand {
|
|
|
276
351
|
unit === "d" ? day :
|
|
277
352
|
unit === "m" ? month :
|
|
278
353
|
year;
|
|
279
|
-
const removed = Task.purge(n * factor);
|
|
354
|
+
const removed = Task.purge(this.log, n * factor);
|
|
280
355
|
if (removed.length === 0)
|
|
281
356
|
this.log.write("info", "task: no tasks to purge");
|
|
282
357
|
else
|
|
@@ -300,7 +375,7 @@ export class TaskMCP {
|
|
|
300
375
|
description: "List all persisted tasks. " +
|
|
301
376
|
"Returns a `tasks` array (in lexicographic `id` order) where each item has the " +
|
|
302
377
|
"task `id`. If `verbose` is `true`, each item additionally has an `mtime` field " +
|
|
303
|
-
"(last modification time of the task's `
|
|
378
|
+
"(last modification time of the task's `TASK-<id>.md` file, formatted as `YYYY-MM-DD HH:MM`). " +
|
|
304
379
|
"Returns an empty array if no tasks exist.",
|
|
305
380
|
inputSchema: {
|
|
306
381
|
verbose: z.boolean().optional()
|
|
@@ -310,13 +385,13 @@ export class TaskMCP {
|
|
|
310
385
|
tasks: z.array(z.object({
|
|
311
386
|
id: z.string().describe("task identifier"),
|
|
312
387
|
mtime: z.string().optional()
|
|
313
|
-
.describe("
|
|
388
|
+
.describe("`TASK-<id>.md` modification time (`YYYY-MM-DD HH:MM`); only present if `verbose` is true")
|
|
314
389
|
})).describe("all persisted tasks in lexicographic id order")
|
|
315
390
|
}
|
|
316
391
|
}, async (args) => {
|
|
317
392
|
try {
|
|
318
393
|
const verbose = args.verbose ?? false;
|
|
319
|
-
const items = Task.list(verbose);
|
|
394
|
+
const items = Task.list(this.log, verbose);
|
|
320
395
|
const tasks = verbose ?
|
|
321
396
|
items.map((item) => ({ id: item.id, mtime: item.mtime ?? "" })) :
|
|
322
397
|
items.map((item) => ({ id: item.id }));
|
|
@@ -345,7 +420,7 @@ export class TaskMCP {
|
|
|
345
420
|
}
|
|
346
421
|
}, async (args) => {
|
|
347
422
|
try {
|
|
348
|
-
const text = Task.load(args.id);
|
|
423
|
+
const text = Task.load(this.log, args.id);
|
|
349
424
|
return {
|
|
350
425
|
content: [{ type: "text", text }]
|
|
351
426
|
};
|
|
@@ -371,7 +446,7 @@ export class TaskMCP {
|
|
|
371
446
|
}
|
|
372
447
|
}, async (args) => {
|
|
373
448
|
try {
|
|
374
|
-
Task.save(args.id, args.text);
|
|
449
|
+
Task.save(this.log, args.id, args.text);
|
|
375
450
|
return {
|
|
376
451
|
content: [{ type: "text", text: `task_save: OK: saved task "${args.id}"` }]
|
|
377
452
|
};
|
|
@@ -395,7 +470,7 @@ export class TaskMCP {
|
|
|
395
470
|
}
|
|
396
471
|
}, async (args) => {
|
|
397
472
|
try {
|
|
398
|
-
const removed = Task.delete(args.id);
|
|
473
|
+
const removed = Task.delete(this.log, args.id);
|
|
399
474
|
const msg = removed ?
|
|
400
475
|
`task_delete: OK: removed task "${args.id}"` :
|
|
401
476
|
`task_delete: WARNING: no task "${args.id}" to remove`;
|
|
@@ -415,7 +490,7 @@ export class TaskMCP {
|
|
|
415
490
|
mcp.registerTool("ase_task_rename", {
|
|
416
491
|
title: "ASE task rename",
|
|
417
492
|
description: "Rename a previously persisted task from `old` to `new` by atomically moving the " +
|
|
418
|
-
"task
|
|
493
|
+
"task `TASK-<id>.md` file. Returns a status `text` indicating whether the rename succeeded. " +
|
|
419
494
|
"Fails with an error if the target id already exists.",
|
|
420
495
|
inputSchema: {
|
|
421
496
|
old: z.string()
|
|
@@ -425,7 +500,7 @@ export class TaskMCP {
|
|
|
425
500
|
}
|
|
426
501
|
}, async (args) => {
|
|
427
502
|
try {
|
|
428
|
-
const renamed = Task.rename(args.old, args.new);
|
|
503
|
+
const renamed = Task.rename(this.log, args.old, args.new);
|
|
429
504
|
const msg = renamed ?
|
|
430
505
|
`task_rename: OK: renamed task "${args.old}" to "${args.new}"` :
|
|
431
506
|
`task_rename: WARNING: no task "${args.old}" to rename`;
|
package/dst/ase.js
CHANGED
|
@@ -14,6 +14,7 @@ import DiagramCommand from "./ase-diagram.js";
|
|
|
14
14
|
import SetupCommand from "./ase-setup.js";
|
|
15
15
|
import StatuslineCommand from "./ase-statusline.js";
|
|
16
16
|
import TaskCommand from "./ase-task.js";
|
|
17
|
+
import ArtifactCommand from "./ase-artifact.js";
|
|
17
18
|
import pkg from "../package.json" with { type: "json" };
|
|
18
19
|
/* globally initialize logger */
|
|
19
20
|
const log = new Log("ase", "warning", "-");
|
|
@@ -49,6 +50,7 @@ const main = async () => {
|
|
|
49
50
|
new HookCommand(log).register(program);
|
|
50
51
|
new StatuslineCommand(log).register(program);
|
|
51
52
|
new TaskCommand(log).register(program);
|
|
53
|
+
new ArtifactCommand(log).register(program);
|
|
52
54
|
new DiagramCommand(log).register(program);
|
|
53
55
|
/* parse program arguments */
|
|
54
56
|
await program.parseAsync(process.argv);
|
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.6",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"@types/shell-quote": "1.7.5",
|
|
38
38
|
"@types/proper-lockfile": "4.1.4",
|
|
39
39
|
"@types/write-file-atomic": "4.0.3",
|
|
40
|
-
"@types/pacote": "11.1.8"
|
|
40
|
+
"@types/pacote": "11.1.8",
|
|
41
|
+
"@types/picomatch": "4.0.3"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"commander": "15.0.0",
|
|
@@ -60,7 +61,8 @@
|
|
|
60
61
|
"proper-lockfile": "4.1.2",
|
|
61
62
|
"write-file-atomic": "8.0.0",
|
|
62
63
|
"pacote": "21.5.0",
|
|
63
|
-
"ofetch": "1.5.1"
|
|
64
|
+
"ofetch": "1.5.1",
|
|
65
|
+
"picomatch": "4.0.4"
|
|
64
66
|
},
|
|
65
67
|
"engines": {
|
|
66
68
|
"npm": ">=10.0.0",
|
|
@@ -16,12 +16,15 @@ Artifact Meta Information
|
|
|
16
16
|
(SAS)", "Architecture Description", or "Architecture Decision
|
|
17
17
|
Record (ADR)".
|
|
18
18
|
|
|
19
|
-
- `
|
|
19
|
+
- `Source Code` (`CODE`), aka "Software Implementation Results (IMP)",
|
|
20
|
+
"Code", or "Software".
|
|
20
21
|
|
|
21
22
|
- `Documentation` (`DOCS`), aka "Software Documentation Results (DOC)".
|
|
22
23
|
|
|
24
|
+
- `Tasks` (`TASK`), aka "Task Plans", "Issues", or "User Stories".
|
|
25
|
+
|
|
23
26
|
Each **Artifact Set** has a unique identifier <artifact-set-id/>,
|
|
24
|
-
which is one of `SPEC`, `ARCH`, `
|
|
27
|
+
which is one of `SPEC`, `ARCH`, `CODE`, `DOCS`, or `TASK`.
|
|
25
28
|
|
|
26
29
|
- **Artifact**:
|
|
27
30
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Task
|
|
3
|
+
----
|
|
4
4
|
|
|
5
|
-
Every *task
|
|
5
|
+
Every *task* uses a strict and fixed format:
|
|
6
6
|
|
|
7
7
|
<format>
|
|
8
8
|
|
|
9
|
-
# ✪ TASK
|
|
9
|
+
# ✪ TASK <task-id/>: <title/>
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
✳ Created: **<timestamp-created/>**
|
|
12
|
+
✎ Modified: **<timestamp-modified/>**
|
|
12
13
|
|
|
13
14
|
## ※ CONTEXT
|
|
14
15
|
|
|
@@ -30,7 +31,7 @@ Every *task plan* uses a strict and fixed format:
|
|
|
30
31
|
|
|
31
32
|
</format>
|
|
32
33
|
|
|
33
|
-
You *MUST* honor the following hints on this *task
|
|
34
|
+
You *MUST* honor the following hints on this *task* format:
|
|
34
35
|
|
|
35
36
|
- You *MUST* always keep the first empty line and the last empty line.
|
|
36
37
|
If one of them is missing, add it back.
|
package/plugin/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "http://github.com/rse/ase",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "http://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.6",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -299,7 +299,7 @@ interface quality, quality attributes, and architecture governance.
|
|
|
299
299
|
specification <mermaid-spec/> for a `flowchart TB` of the
|
|
300
300
|
high-level component or layer structure and dispatch the rendering
|
|
301
301
|
to the `ase-meta-diagram` sub-agent by calling the tool
|
|
302
|
-
`Agent(name: "ase
|
|
302
|
+
`Agent(name: "ase-meta-diagram", description: "Diagram Rendering",
|
|
303
303
|
subagent_type: "ase:ase-meta-diagram", prompt: <mermaid-spec/>)`,
|
|
304
304
|
using its returned fenced code block verbatim. Show layers /
|
|
305
305
|
slices / major components and their dependency direction.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ase-arch-discover
|
|
3
|
-
argument-hint: "[--help|-h] <functionality>"
|
|
3
|
+
argument-hint: "[--help|-h] [--limit|-l=12] <functionality>"
|
|
4
4
|
description: >
|
|
5
5
|
Discover additional, third-party components (libraries/frameworks) for
|
|
6
6
|
the technology stack to provide needed functionality.
|
|
@@ -16,15 +16,23 @@ allowed-tools:
|
|
|
16
16
|
|
|
17
17
|
@${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
|
|
18
18
|
@${CLAUDE_SKILL_DIR}/../../meta/ase-skill.md
|
|
19
|
+
@${CLAUDE_SKILL_DIR}/../../meta/ase-dialog.md
|
|
20
|
+
@${CLAUDE_SKILL_DIR}/../../meta/ase-getopt.md
|
|
19
21
|
|
|
20
22
|
<skill name="ase-arch-discover">
|
|
21
23
|
Discover Components
|
|
22
24
|
</skill>
|
|
23
25
|
|
|
26
|
+
<expand name="getopt"
|
|
27
|
+
arg1="ase-arch-discover"
|
|
28
|
+
arg2="--limit|-l=12">
|
|
29
|
+
$ARGUMENTS
|
|
30
|
+
</expand>
|
|
31
|
+
|
|
24
32
|
<objective>
|
|
25
33
|
*Discover* additional, *third-party components* (libraries/frameworks)
|
|
26
34
|
for the technology stack to *provide* the *needed functionality*
|
|
27
|
-
<request
|
|
35
|
+
<request><getopt-arguments/></request>.
|
|
28
36
|
</objective>
|
|
29
37
|
|
|
30
38
|
<flow>
|
|
@@ -93,13 +101,13 @@ for the technology stack to *provide* the *needed functionality*
|
|
|
93
101
|
|
|
94
102
|
- Based on the essential keywords <keyword-L/> (L=1-M),
|
|
95
103
|
use the `ase-meta-search` skill in a subagent to *generally*
|
|
96
|
-
discover an initial set of a maximum of
|
|
104
|
+
discover an initial set of a maximum of <getopt-option-limit/> *NPM packages*
|
|
97
105
|
<component-K/> and at least their real name <name-K/> and
|
|
98
106
|
their unique package names <package-K/>.
|
|
99
107
|
|
|
100
|
-
- Use the shell command `npm search --json --searchlimit
|
|
108
|
+
- Use the shell command `npm search --json --searchlimit <getopt-option-limit/>
|
|
101
109
|
"<keyword-1/>" [...] "<keyword-M/>"` to *specifically*
|
|
102
|
-
discover an additional set of a maximum of
|
|
110
|
+
discover an additional set of a maximum of <getopt-option-limit/> *NPM packages*
|
|
103
111
|
<component-K/> and at least their unique package names
|
|
104
112
|
<package-K/>, based on the essential keywords <keyword-L/>
|
|
105
113
|
(L=1-M). Merge the results into the already existing result
|
|
@@ -109,14 +117,14 @@ for the technology stack to *provide* the *needed functionality*
|
|
|
109
117
|
|
|
110
118
|
- Based on the essential keywords <keyword-L/> (L=1-M),
|
|
111
119
|
use the `ase-meta-search` skill in a subagent to *generally*
|
|
112
|
-
discover an initial set of a maximum of
|
|
120
|
+
discover an initial set of a maximum of <getopt-option-limit/> *Maven packages*
|
|
113
121
|
<component-K/> and at least their real name <name-K/> and
|
|
114
122
|
their unique Maven coordinates <package-K/> of the form
|
|
115
123
|
`groupId:artifactId`.
|
|
116
124
|
|
|
117
|
-
- Use the shell command `curl -s 'https://search.maven.org/solrsearch/select?q=<keyword-1/>+<keyword-M/>&rows
|
|
125
|
+
- Use the shell command `curl -s 'https://search.maven.org/solrsearch/select?q=<keyword-1/>+<keyword-M/>&rows=<getopt-option-limit/>&wt=json'`
|
|
118
126
|
to *specifically* discover an additional set of a maximum
|
|
119
|
-
of
|
|
127
|
+
of <getopt-option-limit/> *Maven packages* <component-K/> and at least their
|
|
120
128
|
unique Maven coordinates <package-K/> (i.e. `<g/>:<a/>` from
|
|
121
129
|
each result document's `g` and `a` fields), based on the
|
|
122
130
|
essential keywords <keyword-L/> (L=1-M). Merge the results
|
|
@@ -139,7 +147,7 @@ for the technology stack to *provide* the *needed functionality*
|
|
|
139
147
|
|
|
140
148
|
- Sort, in descending order, the discovered components
|
|
141
149
|
<component-K/> (K=1-N) by their `rank` field and trim the result
|
|
142
|
-
list to just a maximum of
|
|
150
|
+
list to just a maximum of <getopt-option-limit/> total components.
|
|
143
151
|
|
|
144
152
|
- For each component <component-K/> (K=1-N), research and then
|
|
145
153
|
decide which *one* of *USP* (Unique Selling Point -- what makes
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
`ase-arch-discover`
|
|
9
9
|
[`--help`|`-h`]
|
|
10
|
+
[`--limit`|`-l=12`]
|
|
10
11
|
*functionality*
|
|
11
12
|
|
|
12
13
|
## DESCRIPTION
|
|
@@ -23,6 +24,13 @@ stars, dates) via the `ase_component_info` MCP tool, and reports
|
|
|
23
24
|
the top-ranked components as a Markdown table together with a single
|
|
24
25
|
distinguishing hint (USP, Crux, or Gotcha) per component.
|
|
25
26
|
|
|
27
|
+
## OPTIONS
|
|
28
|
+
|
|
29
|
+
`--limit`|`-l=12`:
|
|
30
|
+
The *maximum* number of components searched per source and retained
|
|
31
|
+
in the final ranking (default: 12). Raise it for a broader, more
|
|
32
|
+
exhaustive survey, lower it for a quicker, narrower lookup.
|
|
33
|
+
|
|
26
34
|
## ARGUMENTS
|
|
27
35
|
|
|
28
36
|
*functionality*:
|
|
@@ -43,6 +51,12 @@ Discover components for HTTP client functionality:
|
|
|
43
51
|
❯ /ase-arch-discover HTTP client with retries
|
|
44
52
|
```
|
|
45
53
|
|
|
54
|
+
Discover a broader set of up to 20 HTTP client components:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
❯ /ase-arch-discover --limit 20 HTTP client with retries
|
|
58
|
+
```
|
|
59
|
+
|
|
46
60
|
## SEE ALSO
|
|
47
61
|
|
|
48
62
|
`ase-arch-analyze`, `ase-meta-search`, `ase-meta-evaluate`.
|
|
@@ -38,7 +38,7 @@ From scratch *craft* the following feature:
|
|
|
38
38
|
<feature><getopt-arguments/></feature>
|
|
39
39
|
</objective>
|
|
40
40
|
|
|
41
|
-
@${CLAUDE_SKILL_DIR}/../../meta/ase-format-
|
|
41
|
+
@${CLAUDE_SKILL_DIR}/../../meta/ase-format-task.md
|
|
42
42
|
|
|
43
43
|
Procedure
|
|
44
44
|
---------
|
|
@@ -228,7 +228,7 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
228
228
|
TB`, `stateDiagram-v2`, `sequenceDiagram`, `classDiagram`,
|
|
229
229
|
or `erDiagram`, depending on intent) and dispatching the
|
|
230
230
|
rendering to the `ase-meta-diagram` sub-agent by calling
|
|
231
|
-
the tool `Agent(name: "ase
|
|
231
|
+
the tool `Agent(name: "ase-meta-diagram", description:
|
|
232
232
|
"Diagram Rendering", subagent_type: "ase:ase-meta-diagram",
|
|
233
233
|
prompt: <mermaid-spec/>)`, reproducing its returned fenced
|
|
234
234
|
code block verbatim. For *current vs. proposed* comparisons,
|
|
@@ -89,7 +89,7 @@ Give *insights* into the project through the source code of $ARGUMENTS.
|
|
|
89
89
|
<mermaid-spec/> for a `flowchart TB` diagram with all modules as
|
|
90
90
|
boxes and the imports between modules as the directed edges. Then
|
|
91
91
|
dispatch the rendering to the `ase-meta-diagram` sub-agent by
|
|
92
|
-
calling the tool `Agent(name: "ase
|
|
92
|
+
calling the tool `Agent(name: "ase-meta-diagram", description:
|
|
93
93
|
"Diagram Rendering", subagent_type: "ase:ase-meta-diagram", prompt:
|
|
94
94
|
<mermaid-spec/>)` and reproduce its returned fenced code block
|
|
95
95
|
verbatim in the response text. Do not display any further
|