@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.
Files changed (43) hide show
  1. package/dst/ase-artifact.js +331 -0
  2. package/dst/ase-config.js +56 -10
  3. package/dst/ase-service.js +2 -0
  4. package/dst/ase-task.js +139 -64
  5. package/dst/ase.js +2 -0
  6. package/package.json +5 -3
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/.github/plugin/plugin.json +1 -1
  9. package/plugin/meta/ase-format-meta.md +5 -2
  10. package/plugin/meta/{ase-format-plan.md → ase-format-task.md} +7 -6
  11. package/plugin/package.json +1 -1
  12. package/plugin/skills/ase-arch-analyze/SKILL.md +1 -1
  13. package/plugin/skills/ase-arch-discover/SKILL.md +17 -9
  14. package/plugin/skills/ase-arch-discover/help.md +14 -0
  15. package/plugin/skills/ase-code-craft/SKILL.md +2 -2
  16. package/plugin/skills/ase-code-insight/SKILL.md +1 -1
  17. package/plugin/skills/ase-code-lint/SKILL.md +1 -1
  18. package/plugin/skills/ase-code-refactor/SKILL.md +2 -2
  19. package/plugin/skills/ase-code-resolve/SKILL.md +3 -3
  20. package/plugin/skills/ase-docs-proofread/SKILL.md +1 -1
  21. package/plugin/skills/ase-meta-brainstorm/SKILL.md +22 -21
  22. package/plugin/skills/ase-meta-brainstorm/help.md +38 -12
  23. package/plugin/skills/ase-meta-chat/SKILL.md +1 -1
  24. package/plugin/skills/ase-meta-diaboli/SKILL.md +1 -1
  25. package/plugin/skills/ase-meta-diff/SKILL.md +1 -1
  26. package/plugin/skills/ase-meta-quorum/SKILL.md +37 -5
  27. package/plugin/skills/ase-meta-quorum/help.md +18 -0
  28. package/plugin/skills/ase-meta-review/SKILL.md +1 -1
  29. package/plugin/skills/ase-meta-search/SKILL.md +35 -6
  30. package/plugin/skills/ase-meta-search/help.md +14 -2
  31. package/plugin/skills/ase-meta-steelman/SKILL.md +1 -1
  32. package/plugin/skills/ase-meta-why/SKILL.md +117 -25
  33. package/plugin/skills/ase-meta-why/help.md +30 -2
  34. package/plugin/skills/ase-task-condense/SKILL.md +1 -1
  35. package/plugin/skills/ase-task-delete/help.md +0 -4
  36. package/plugin/skills/ase-task-edit/SKILL.md +4 -4
  37. package/plugin/skills/ase-task-edit/help.md +0 -4
  38. package/plugin/skills/ase-task-grill/SKILL.md +1 -1
  39. package/plugin/skills/ase-task-grill/help.md +0 -4
  40. package/plugin/skills/ase-task-implement/SKILL.md +1 -1
  41. package/plugin/skills/ase-task-implement/help.md +5 -6
  42. package/plugin/skills/ase-task-preflight/SKILL.md +1 -1
  43. 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>/.ase/task/<id>/plan.md */
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(), ".ase", "task");
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
- static path(id) {
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
- return path.join(Task.baseDir(), id, "plan.md");
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; the task's home
54
- directory <project>/.ase/task/<id>/ is owned by ASE and removed
55
- in full by Task.delete, so callers must not place foreign files there */
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 entire task home directory
64
- <project>/.ase/task/<id>/ (owned by ASE); returns true if a task existed */
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(path.dirname(file), { recursive: true, force: true });
135
+ fs.rmSync(file, { force: true });
70
136
  return true;
71
137
  }
72
- /* rename a task by moving the entire task home directory
73
- <project>/.ase/task/<oldId>/ to <project>/.ase/task/<newId>/;
74
- returns true on success, false if the source task does not exist;
75
- throws if the target id already exists */
76
- static rename(oldId, newId) {
77
- const oldDir = path.dirname(Task.path(oldId));
78
- const newDir = path.dirname(Task.path(newId));
79
- if (!fs.existsSync(oldDir))
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(newDir))
148
+ if (fs.existsSync(newFile))
82
149
  throw new Error(`task: target id "${newId}" already exists`);
83
- fs.mkdirSync(path.dirname(newDir), { recursive: true });
84
- fs.renameSync(oldDir, newDir);
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 `plan.md` modification time formatted
89
- as "YYYY-MM-DD HH:MM", otherwise it is left undefined */
90
- static list(verbose = false) {
91
- const dir = Task.baseDir();
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
- if (!/^[A-Za-z0-9-]+$/.test(entry))
97
- continue;
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: entry, mtime });
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
- const dir = Task.baseDir();
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
- if (!/^[A-Za-z0-9-]+$/.test(entry))
120
- continue;
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(sub, { recursive: true, force: true });
130
- removed.push(entry);
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>/.ase/task/<id>/plan.md")
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 plan.md modification time as (YYYY-MM-DD HH:MM)")
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 `plan.md`, formatted as `YYYY-MM-DD HH:MM`). " +
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("plan.md modification time (`YYYY-MM-DD HH:MM`); only present if `verbose` is true")
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 home directory. Returns a status `text` indicating whether the rename succeeded. " +
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.4",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -16,12 +16,15 @@ Artifact Meta Information
16
16
  (SAS)", "Architecture Description", or "Architecture Decision
17
17
  Record (ADR)".
18
18
 
19
- - `Software` (`SOFT`), aka "Software Implementation Results (IMP)".
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`, `SOFT`, or `DOCS`.
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
- Plan Format
3
- -----------
2
+ Task
3
+ ----
4
4
 
5
- Every *task plan* uses a strict and fixed format:
5
+ Every *task* uses a strict and fixed format:
6
6
 
7
7
  <format>
8
8
 
9
- # ✪ TASK PLAN: **<title/>**
9
+ # ✪ TASK <task-id/>: <title/>
10
10
 
11
- ◉ task id: **<task-id/>** // created: **<timestamp-created/>** // ✎ modified: **<timestamp-modified/>**
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 plan* format:
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.
@@ -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.4",
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:ase-meta-diagram", description: "Diagram Rendering",
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>$ARGUMENTS</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 12 *NPM packages*
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 12
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 12 *NPM packages*
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 12 *Maven packages*
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=12&wt=json'`
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 12 *Maven packages* <component-K/> and at least their
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 12 total components.
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-plan.md
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:ase-meta-diagram", description:
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:ase-meta-diagram", description:
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