@rse/ase 2.0.1 → 2.0.3

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 (155) hide show
  1. package/dst/ase-agent-definition-core.js +111 -87
  2. package/dst/ase-agent-fleet.js +1 -1
  3. package/dst/ase-agent-runtime-claim.js +5 -3
  4. package/dst/ase-agent-runtime-engine.js +1 -1
  5. package/dst/ase-agent-runtime-phase.js +1 -1
  6. package/dst/ase-agent-runtime-request.js +1 -1
  7. package/dst/ase-agent-runtime.js +3 -5
  8. package/dst/ase-artifact.js +3 -3
  9. package/dst/ase-config-core.js +2 -16
  10. package/dst/ase-config-schema.js +1 -3
  11. package/dst/ase-config.js +4 -1
  12. package/dst/ase-hook.js +14 -19
  13. package/dst/ase-lib-daemon-cli.js +117 -4
  14. package/dst/ase-lib-server-cli.js +10 -10
  15. package/dst/ase-mcp.js +4 -4
  16. package/dst/ase-service-getopt.js +2 -2
  17. package/dst/ase-service-otel.js +4 -4
  18. package/dst/ase-service-skills.js +5 -15
  19. package/dst/ase-service.js +87 -210
  20. package/dst/ase-setup-common.js +1 -2
  21. package/dst/ase-setup-settings.js +6 -9
  22. package/dst/ase-setup.js +2 -3
  23. package/dst/ase-statusline.js +170 -170
  24. package/dst/ase-task-board-core.js +1 -1
  25. package/dst/ase-task-board-flow.js +7 -6
  26. package/dst/ase-task-board-graph.js +5 -4
  27. package/dst/ase-task-board-tui-control.js +51 -45
  28. package/dst/ase-task-board-tui-model.js +195 -181
  29. package/dst/ase-task-board-tui-popup.js +2 -3
  30. package/dst/ase-task-board-tui-view.js +88 -79
  31. package/dst/ase-task-board-web-client.css +1 -1
  32. package/dst/ase-task-board-web-client.js +2 -2
  33. package/dst/ase-task-board-web-server.js +4 -27
  34. package/dst/ase-task-board.js +1 -1
  35. package/dst/ase-task-format.js +1 -1
  36. package/dst/ase-task-store-client.js +17 -5
  37. package/dst/ase-task-store-core.js +5 -7
  38. package/dst/ase-task-store-issues.js +91 -0
  39. package/dst/ase-task-store-plugin-ase.js +7 -3
  40. package/dst/ase-task-store-plugin-gitea.js +11 -97
  41. package/dst/ase-task-store-plugin-github.js +5 -77
  42. package/dst/ase-task-store-plugin-gitlab.js +4 -83
  43. package/dst/ase-task.js +51 -23
  44. package/dst/ase-util-compat.js +1 -1
  45. package/dst/ase-util-diagram.js +16 -42
  46. package/dst/ase.js +5 -5
  47. package/dst/man/ase-agent-adhoc.1 +1 -1
  48. package/dst/man/ase-agent-condenser.1 +1 -1
  49. package/dst/man/ase-agent-editor.1 +1 -1
  50. package/dst/man/ase-agent-estimator.1 +1 -1
  51. package/dst/man/ase-agent-fleet.1 +1 -1
  52. package/dst/man/ase-agent-implementer.1 +1 -1
  53. package/dst/man/ase-agent-integrator.1 +1 -1
  54. package/dst/man/ase-agent-qualifier.1 +1 -1
  55. package/dst/man/ase-agent-rebooter.1 +1 -1
  56. package/dst/man/ase-agent-relay.1 +1 -1
  57. package/dst/man/ase-agent-researcher.1 +1 -1
  58. package/dst/man/ase-agent.1 +1 -1
  59. package/dst/man/ase-artifact-list.1 +1 -1
  60. package/dst/man/ase-artifact-name.1 +1 -1
  61. package/dst/man/ase-artifact.1 +1 -1
  62. package/dst/man/ase-config-delete.1 +1 -1
  63. package/dst/man/ase-config-edit.1 +1 -1
  64. package/dst/man/ase-config-get.1 +1 -1
  65. package/dst/man/ase-config-init.1 +1 -1
  66. package/dst/man/ase-config-list.1 +1 -1
  67. package/dst/man/ase-config-set.1 +1 -1
  68. package/dst/man/ase-config.1 +1 -1
  69. package/dst/man/ase-hook-permission-request.1 +1 -1
  70. package/dst/man/ase-hook-pre-tool-use.1 +1 -1
  71. package/dst/man/ase-hook-session-end.1 +1 -1
  72. package/dst/man/ase-hook-session-persona.1 +1 -1
  73. package/dst/man/ase-hook-session-start.1 +1 -1
  74. package/dst/man/ase-hook-stop.1 +1 -1
  75. package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
  76. package/dst/man/ase-hook.1 +1 -1
  77. package/dst/man/ase-man.1 +1 -1
  78. package/dst/man/ase-mcp.1 +1 -1
  79. package/dst/man/ase-service-log.1 +1 -1
  80. package/dst/man/ase-service-send.1 +1 -1
  81. package/dst/man/ase-service-start.1 +1 -1
  82. package/dst/man/ase-service-status.1 +1 -1
  83. package/dst/man/ase-service-stop.1 +1 -1
  84. package/dst/man/ase-service.1 +1 -1
  85. package/dst/man/ase-setup-disable.1 +1 -1
  86. package/dst/man/ase-setup-enable.1 +1 -1
  87. package/dst/man/ase-setup-install.1 +1 -1
  88. package/dst/man/ase-setup-mcp.1 +1 -1
  89. package/dst/man/ase-setup-status.1 +1 -1
  90. package/dst/man/ase-setup-statusline.1 +1 -1
  91. package/dst/man/ase-setup-telemetry.1 +1 -1
  92. package/dst/man/ase-setup-uninstall.1 +1 -1
  93. package/dst/man/ase-setup-update.1 +1 -1
  94. package/dst/man/ase-setup.1 +1 -1
  95. package/dst/man/ase-spec-export.1 +1 -1
  96. package/dst/man/ase-spec-lint.1 +1 -1
  97. package/dst/man/ase-spec-preview.1 +1 -1
  98. package/dst/man/ase-spec.1 +1 -1
  99. package/dst/man/ase-statusline.1 +1 -1
  100. package/dst/man/ase-task-assign.1 +1 -1
  101. package/dst/man/ase-task-board.1 +3 -3
  102. package/dst/man/ase-task-delete.1 +1 -1
  103. package/dst/man/ase-task-edit.1 +1 -1
  104. package/dst/man/ase-task-idscheme.1 +1 -1
  105. package/dst/man/ase-task-lifecycle.1 +1 -1
  106. package/dst/man/ase-task-list.1 +1 -1
  107. package/dst/man/ase-task-load.1 +1 -1
  108. package/dst/man/ase-task-newid.1 +1 -1
  109. package/dst/man/ase-task-purge.1 +1 -1
  110. package/dst/man/ase-task-rename.1 +1 -1
  111. package/dst/man/ase-task-request.1 +1 -1
  112. package/dst/man/ase-task-save.1 +1 -1
  113. package/dst/man/ase-task-status.1 +1 -1
  114. package/dst/man/ase-task-store.1 +1 -1
  115. package/dst/man/ase-task-view.1 +1 -1
  116. package/dst/man/ase-task.1 +1 -1
  117. package/dst/man/ase-util-compat.1 +1 -1
  118. package/dst/man/ase-util-diagram.1 +1 -1
  119. package/dst/man/ase-util-meta.1 +1 -1
  120. package/dst/man/ase-util-metric.1 +1 -1
  121. package/dst/man/ase-util-mint.1 +1 -1
  122. package/dst/man/ase-util-worktree.1 +1 -1
  123. package/dst/man/ase-util.1 +1 -1
  124. package/dst/man/ase.1 +1 -1
  125. package/package.json +1 -1
  126. package/plugin/.claude-plugin/plugin.json +1 -1
  127. package/plugin/.codex-plugin/plugin.json +1 -1
  128. package/plugin/.github/plugin/plugin.json +1 -1
  129. package/plugin/meta/ase-common-resolve.md +2 -2
  130. package/plugin/meta/ase-skill.md +7 -0
  131. package/plugin/mods/ase-mods.ts +16 -12
  132. package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
  133. package/plugin/skills/ase-code-analyze/SKILL.md +26 -10
  134. package/plugin/skills/ase-code-analyze/help.md +11 -6
  135. package/plugin/skills/ase-code-edit/SKILL.md +5 -4
  136. package/plugin/skills/ase-code-edit/help.md +1 -1
  137. package/plugin/skills/ase-code-lint/SKILL.md +52 -27
  138. package/plugin/skills/ase-code-lint/help.md +10 -5
  139. package/plugin/skills/ase-help-intent/data.md +60 -30
  140. package/plugin/skills/ase-help-skill/help.md +3 -4
  141. package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
  142. package/plugin/skills/ase-meta-quorum/help.md +5 -3
  143. package/plugin/skills/ase-meta-workflow/sample.md +5 -3
  144. package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
  145. package/plugin/skills/ase-repo-changelog/help.md +2 -2
  146. package/plugin/skills/ase-repo-review/SKILL.md +5 -3
  147. package/plugin/skills/ase-repo-review/help.md +3 -1
  148. package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
  149. package/plugin/skills/ase-spec-edit/help.md +2 -1
  150. package/plugin/skills/ase-sync-reconcile/help.md +4 -4
  151. package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
  152. package/plugin/skills/ase-task-grill/help.md +1 -1
  153. package/plugin/skills/ase-task-implement/SKILL.md +58 -1
  154. package/plugin/skills/ase-task-implement/help.md +16 -0
  155. package/plugin/skills/ase-task-list/help.md +2 -2
@@ -38,10 +38,8 @@ const live = (issue) => (issue.issue_type ?? "issue") === "issue" && !issue.labe
38
38
  "Created"/"Modified" onto the issue timestamps, any other key (including "Group",
39
39
  as GitLab has no parent issues) onto "ase:<key>:<value>" labels, and the attachments
40
40
  onto the issue notes */
41
- class GitLabTaskStoragePlugin {
42
- ctx;
41
+ class GitLabTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
43
42
  name = "gitlab";
44
- url;
45
43
  token;
46
44
  source;
47
45
  issues;
@@ -51,13 +49,6 @@ class GitLabTaskStoragePlugin {
51
49
  milestones;
52
50
  members;
53
51
  users;
54
- repos = new Map();
55
- poll;
56
- listener = null;
57
- timer = null;
58
- polling = false;
59
- opened = now();
60
- polls = new Map();
61
52
  registry = new LRUCache({ max: 64, ttl: 60 * 1000 });
62
53
  latest = new Map();
63
54
  titles = new Map();
@@ -67,7 +58,7 @@ class GitLabTaskStoragePlugin {
67
58
  recent = new Map();
68
59
  loads = new LRUCache({ max: 256, ttl: 2 * 1000 });
69
60
  constructor(ctx) {
70
- this.ctx = ctx;
61
+ super(ctx, "projects", now);
71
62
  const options = ctx.options;
72
63
  const token = options.token ?? process.env.GITLAB_TOKEN ?? "";
73
64
  if (typeof token !== "string" || token === "")
@@ -132,27 +123,6 @@ class GitLabTaskStoragePlugin {
132
123
  }
133
124
  });
134
125
  }
135
- /* the storage lifecycle: polling for changes while opened and observed
136
- (without keeping the process alive) */
137
- async open() {
138
- this.opened = now();
139
- if (this.poll > 0 && this.listener !== null) {
140
- this.timer = setInterval(() => {
141
- this.pollAll().catch(() => { });
142
- }, this.poll * 1000);
143
- this.timer.unref();
144
- }
145
- this.ctx.log("debug", `opened ${this.repos.size} projects on ${this.url} (polling every ${this.poll}s)`);
146
- }
147
- async close() {
148
- if (this.timer !== null)
149
- clearInterval(this.timer);
150
- this.timer = null;
151
- }
152
- /* observe the changes of the issues made outside of this plugin instance */
153
- watch(listener) {
154
- this.listener = listener;
155
- }
156
126
  /* ==== project registry ==== */
157
127
  /* the GitLab project path of a project */
158
128
  repo(prjId) {
@@ -224,15 +194,6 @@ class GitLabTaskStoragePlugin {
224
194
  this.latest.set(path, { etag: r.etag, number, at: Date.now() });
225
195
  return number;
226
196
  }
227
- async projectList() {
228
- const out = [];
229
- for (const prjId of this.repos.keys()) {
230
- const entry = await this.projectGet(prjId);
231
- if (entry !== null)
232
- out.push(entry);
233
- }
234
- return out;
235
- }
236
197
  /* get a project, with the highest issue number as the sequence number high-water
237
198
  mark, so the next allocated task id is the one of the next created issue */
238
199
  async projectGet(prjId) {
@@ -279,14 +240,7 @@ class GitLabTaskStoragePlugin {
279
240
  const key = `${path}#${issue.iid}@${issue.updated_at}`;
280
241
  if (fresh)
281
242
  this.linked.delete(key);
282
- let links = this.linked.get(key);
283
- if (links === undefined) {
284
- links = this.links.all(path, issue.iid).then((data) => data);
285
- this.linked.set(key, links);
286
- links.catch(() => {
287
- this.linked.delete(key);
288
- });
289
- }
243
+ const links = TaskIssues.memoize(this.linked, key, () => this.links.all(path, issue.iid).then((data) => data));
290
244
  return (await links).filter((link) => link.link_type === "is_blocked_by" && link.project_id === issue.project_id);
291
245
  }
292
246
  /* the (non-system) notes of an issue */
@@ -490,11 +444,7 @@ class GitLabTaskStoragePlugin {
490
444
  own write or detected change of the issue), as e.g. the boards load the plan
491
445
  and its attachments in separate operations */
492
446
  load(prjId, path, n) {
493
- const key = `${prjId}#${n}`;
494
- const cached = this.loads.get(key);
495
- if (cached !== undefined)
496
- return cached;
497
- const load = (async () => {
447
+ return TaskIssues.memoize(this.loads, `${prjId}#${n}`, async () => {
498
448
  const [issue, notes] = await Promise.all([
499
449
  this.fetch(path, n),
500
450
  n === 0 ? [] : found(this.comments(path, n))
@@ -502,12 +452,7 @@ class GitLabTaskStoragePlugin {
502
452
  if (issue === null)
503
453
  return null;
504
454
  return { issue, after: await this.blockers(path, issue), notes: notes ?? [] };
505
- })();
506
- this.loads.set(key, load);
507
- load.catch(() => {
508
- this.loads.delete(key);
509
455
  });
510
- return load;
511
456
  }
512
457
  /* ==== task plans ==== */
513
458
  async taskList(prjId) {
@@ -596,30 +541,6 @@ class GitLabTaskStoragePlugin {
596
541
  throw new Error(`task "${oldId}" cannot be renamed to "${newId}", as task ids are GitLab issue numbers`);
597
542
  }
598
543
  /* ==== change detection ==== */
599
- /* the change detection state of a project (starting at the opening time) */
600
- pollState(prjId) {
601
- let state = this.polls.get(prjId);
602
- if (state === undefined) {
603
- state = { since: this.opened, seen: new Map() };
604
- this.polls.set(prjId, state);
605
- }
606
- return state;
607
- }
608
- /* poll all registered projects, never overlapping itself */
609
- async pollAll() {
610
- if (this.polling)
611
- return;
612
- this.polling = true;
613
- try {
614
- for (const prjId of this.repos.keys())
615
- await this.pollProject(prjId).catch((err) => {
616
- this.ctx.log("warning", `polling project "${prjId}" failed: ${err instanceof Error ? err.message : String(err)}`);
617
- });
618
- }
619
- finally {
620
- this.polling = false;
621
- }
622
- }
623
544
  /* poll the issues of a project updated since the last poll, conditionally via
624
545
  the entity tag of the last poll (a "304" answer still counts against the rate
625
546
  limit of GitLab), and report the issues not seen at their update time yet: as
package/dst/ase-task.js CHANGED
@@ -341,7 +341,7 @@ export class Task {
341
341
  try {
342
342
  url = new URL(store.value);
343
343
  }
344
- catch (_err) {
344
+ catch {
345
345
  throw unsupported();
346
346
  }
347
347
  if (url.hostname === "" || url.port === "" || url.username !== "" || url.password !== ""
@@ -444,21 +444,12 @@ export class Task {
444
444
  tag: Core.taskTag(plan)
445
445
  };
446
446
  }
447
- /* save a task as text under the given id; throws if its "Status:"
448
- frontmatter key is unknown to the task lifecycle model or not
449
- reachable from the previous status, on a conditional save (with
450
- an entity tag) a problem 412 if the task was changed in the meantime,
451
- and on a create-only save an error if the task already exists;
452
- returns a warning (else empty) if the id does not conform to the task id scheme,
453
- the entity tag of the just-saved plan, and the text of the just-saved plan
454
- (as sent, re-serialized from its parsed form, i.e., before any
455
- normalization by the task store, and with its elided attachment
456
- payloads still elided, while they are restored from the stored
457
- plan for the save itself); the entity tag
458
- is the one the task store reports for the plan as stored, and only on a
459
- task store server predating this report the one of the plan as sent (which
460
- never is the one of a plan saved by somebody else in the meantime, so a
461
- conditional save based on it at worst is refused spuriously) */
447
+ /* save a task as text under the given id, throwing on a "Status:" unknown to or not
448
+ reachable in the task lifecycle model, on a conditional save (with an entity tag) a
449
+ problem 412 on a concurrent change, and on a create-only save an error if the task
450
+ exists; elided attachment payloads are restored from the stored plan; returns the
451
+ id scheme warning (else empty), the entity tag of the saved plan (as reported by the
452
+ task store, else of the plan as sent), and the text of the plan as sent */
462
453
  static async save(log, id, text, tag, create = false) {
463
454
  if (typeof text !== "string")
464
455
  throw new Error("task: text must be a string");
@@ -993,6 +984,17 @@ export default class TaskCommand {
993
984
  process.exit(0);
994
985
  }
995
986
  });
987
+ /* register the further CLI sub-commands */
988
+ this.registerRequestCommands(task);
989
+ this.registerPlanCommands(task);
990
+ this.registerProjectCommands(task);
991
+ /* register CLI sub-command group "ase task store" */
992
+ new TaskStoreCommand(this.log).register(task);
993
+ /* provide "ase task" command for further sub-commands */
994
+ return task;
995
+ }
996
+ /* register the CLI sub-commands requesting and assigning a task */
997
+ registerRequestCommands(task) {
996
998
  /* register CLI sub-command "ase task request" */
997
999
  task
998
1000
  .command("request")
@@ -1049,6 +1051,9 @@ export default class TaskCommand {
1049
1051
  }
1050
1052
  process.exit(0);
1051
1053
  });
1054
+ }
1055
+ /* register the CLI sub-commands loading, viewing, editing, saving, deleting, and renaming a task */
1056
+ registerPlanCommands(task) {
1052
1057
  /* register CLI sub-command "ase task load" */
1053
1058
  task
1054
1059
  .command("load")
@@ -1130,6 +1135,9 @@ export default class TaskCommand {
1130
1135
  this.log.write("info", `task: no task "${oldId}" to rename`);
1131
1136
  process.exit(result !== null ? 0 : 1);
1132
1137
  });
1138
+ }
1139
+ /* register the CLI sub-commands of the task ids, the purging, and the project settings */
1140
+ registerProjectCommands(task) {
1133
1141
  /* register CLI sub-command "ase task newid" */
1134
1142
  task
1135
1143
  .command("newid")
@@ -1197,10 +1205,6 @@ export default class TaskCommand {
1197
1205
  process.exit(0);
1198
1206
  }
1199
1207
  });
1200
- /* register CLI sub-command group "ase task store" */
1201
- new TaskStoreCommand(this.log).register(task);
1202
- /* provide "ase task" command for further sub-commands */
1203
- return task;
1204
1208
  }
1205
1209
  }
1206
1210
  /* render a caught error as an MCP tool error result */
@@ -1264,9 +1268,8 @@ export class TaskMCP {
1264
1268
  try {
1265
1269
  const verbose = args.verbose ?? false;
1266
1270
  const items = await Task.list(this.log);
1267
- const tasks = verbose ?
1268
- items.map((item) => ({ id: item.id, status: item.status, title: item.title, mtime: item.mtime, effort: item.effort })) :
1269
- items.map((item) => ({ id: item.id, status: item.status, title: item.title }));
1271
+ const tasks = verbose ? items :
1272
+ items.map(({ id, status, title }) => ({ id, status, title }));
1270
1273
  const result = { tasks };
1271
1274
  return {
1272
1275
  structuredContent: result,
@@ -1277,6 +1280,16 @@ export class TaskMCP {
1277
1280
  return mcpError(err);
1278
1281
  }
1279
1282
  });
1283
+ /* register the further MCP tools */
1284
+ this.registerLoadTool(mcp);
1285
+ this.registerSaveTool(mcp);
1286
+ this.registerPlanTools(mcp);
1287
+ this.registerStatusTools(mcp);
1288
+ this.registerAssignTool(mcp);
1289
+ this.registerIdTools(mcp);
1290
+ }
1291
+ /* register the MCP tool loading a task plan */
1292
+ registerLoadTool(mcp) {
1280
1293
  /* task load */
1281
1294
  mcp.registerTool("ase_task_load", {
1282
1295
  title: "ASE task load",
@@ -1332,6 +1345,9 @@ export class TaskMCP {
1332
1345
  return mcpError(err);
1333
1346
  }
1334
1347
  });
1348
+ }
1349
+ /* register the MCP tool saving a task plan */
1350
+ registerSaveTool(mcp) {
1335
1351
  /* task save */
1336
1352
  mcp.registerTool("ase_task_save", {
1337
1353
  title: "ASE task save",
@@ -1408,6 +1424,9 @@ export class TaskMCP {
1408
1424
  return mcpError(err);
1409
1425
  }
1410
1426
  });
1427
+ }
1428
+ /* register the MCP tools reading an attachment of, deleting, and renaming a task plan */
1429
+ registerPlanTools(mcp) {
1411
1430
  /* task attachment */
1412
1431
  mcp.registerTool("ase_task_attachment", {
1413
1432
  title: "ASE task attachment",
@@ -1482,6 +1501,9 @@ export class TaskMCP {
1482
1501
  return mcpError(err);
1483
1502
  }
1484
1503
  });
1504
+ }
1505
+ /* register the MCP tools getting/setting the status of and requesting the processing of a task plan */
1506
+ registerStatusTools(mcp) {
1485
1507
  /* task status get/set */
1486
1508
  mcp.registerTool("ase_task_status", {
1487
1509
  title: "ASE task status get/set",
@@ -1539,6 +1561,9 @@ export class TaskMCP {
1539
1561
  return mcpError(err);
1540
1562
  }
1541
1563
  });
1564
+ }
1565
+ /* register the MCP tool assigning a task plan (with AI usage accounting) */
1566
+ registerAssignTool(mcp) {
1542
1567
  /* task assignment (with AI usage accounting) */
1543
1568
  mcp.registerTool("ase_task_assign", {
1544
1569
  title: "ASE task assignment",
@@ -1586,6 +1611,9 @@ export class TaskMCP {
1586
1611
  return mcpError(err);
1587
1612
  }
1588
1613
  });
1614
+ }
1615
+ /* register the MCP tools generating task ids and getting/setting the active task id */
1616
+ registerIdTools(mcp) {
1589
1617
  /* task id generation */
1590
1618
  mcp.registerTool("ase_task_newid", {
1591
1619
  title: "ASE task id generation",
@@ -50,7 +50,7 @@ export default class CompatCommand {
50
50
  });
51
51
  }
52
52
  }
53
- /* MCP tool "ase_compat" — lets the skill call this without a Bash tool */
53
+ /* MCP tool "ase_compat" -- lets the skill call this without a Bash tool */
54
54
  export class CompatMCP {
55
55
  register(mcp) {
56
56
  mcp.registerTool("ase_compat", {
@@ -4,9 +4,9 @@
4
4
  ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
5
  */
6
6
  import fs from "node:fs";
7
- import { InvalidArgumentError } from "commander";
7
+ import { InvalidArgumentError, Option } from "commander";
8
8
  import { z } from "zod";
9
- import { readStdin } from "./ase-lib-stdio.js";
9
+ import { readStdin, writeStdout } from "./ase-lib-stdio.js";
10
10
  /* custom argument parser for Commander: non-negative integer */
11
11
  const parseInteger = (name) => (value) => {
12
12
  const n = Number.parseInt(value, 10);
@@ -14,24 +14,6 @@ const parseInteger = (name) => (value) => {
14
14
  throw new InvalidArgumentError(`${name} must be a non-negative integer`);
15
15
  return n;
16
16
  };
17
- /* custom argument parser for Commander: color mode */
18
- const parseColorMode = (name) => (value) => {
19
- if (value !== "none" && value !== "ansi16" && value !== "ansi256")
20
- throw new InvalidArgumentError(`${name} must be "none", "ansi16", or "ansi256"`);
21
- return value;
22
- };
23
- /* custom argument parser for Commander: diagram language */
24
- const parseLang = (name) => (value) => {
25
- if (value !== "mermaid" && value !== "d2")
26
- throw new InvalidArgumentError(`${name} must be "mermaid" or "d2"`);
27
- return value;
28
- };
29
- /* custom argument parser for Commander: output format */
30
- const parseFormat = (name) => (value) => {
31
- if (value !== "ascii" && value !== "svg")
32
- throw new InvalidArgumentError(`${name} must be "ascii" or "svg"`);
33
- return value;
34
- };
35
17
  /* the D2 compiler, created on first use only (as it spins up the
36
18
  WebAssembly build of D2 in a worker thread, which keeps the process
37
19
  alive until the compiler is disposed) */
@@ -115,8 +97,9 @@ const visibleWidth = (line) => {
115
97
  }
116
98
  return visible;
117
99
  };
118
- /* reusable functionality: Mermaid diagram rendering as Unicode/ASCII art */
100
+ /* reusable functionality: Mermaid and D2 diagram rendering as Unicode/ASCII art or SVG */
119
101
  export class Diagram {
102
+ /* detect a terminal dimension from an environment variable, else from stdout (0 if unknown) */
120
103
  static detectTermDimension(envVar, stdoutProp) {
121
104
  let value = 0;
122
105
  /* attempt 1: query environment variable */
@@ -144,23 +127,13 @@ export class Diagram {
144
127
  }
145
128
  /* detect terminal color capability */
146
129
  static detectColorMode() {
147
- let mode = "none";
148
- let explicit = false;
149
130
  /* attempt 1: query environment variable (explicitly) */
150
- if (process.env.ASE_TERM_COLORS !== undefined)
151
- if (/^(?:none|ansi16|ansi256)$/.test(process.env.ASE_TERM_COLORS)) {
152
- mode = process.env.ASE_TERM_COLORS;
153
- explicit = true;
154
- }
131
+ const env = process.env.ASE_TERM_COLORS;
132
+ if (env === "none" || env === "ansi16" || env === "ansi256")
133
+ return env;
155
134
  /* attempt 2: query stdout */
156
- if (!explicit && process.stdout.isTTY) {
157
- const depth = process.stdout.getColorDepth();
158
- if (depth >= 8)
159
- mode = "ansi256";
160
- else if (depth >= 4)
161
- mode = "ansi16";
162
- }
163
- return mode;
135
+ const depth = process.stdout.isTTY ? process.stdout.getColorDepth() : 0;
136
+ return depth >= 8 ? "ansi256" : depth >= 4 ? "ansi16" : "none";
164
137
  }
165
138
  /* dispose the D2 compiler (if any), so its worker thread no longer
166
139
  keeps the process alive (used by the short-lived CLI only) */
@@ -287,10 +260,13 @@ export default class DiagramCommand {
287
260
  .command("diagram")
288
261
  .description("Render Mermaid or D2 diagram specification as Unicode/ASCII art or SVG")
289
262
  .option("-i, --input <file>", "read diagram source from file instead of stdin")
290
- .option("-l, --lang <lang>", "diagram language (\"mermaid\" or \"d2\")", parseLang("--lang"), "mermaid")
291
- .option("-f, --format <format>", "output format (\"ascii\" or \"svg\")", parseFormat("--format"), "ascii")
263
+ .addOption(new Option("-l, --lang <lang>", "diagram language")
264
+ .choices(["mermaid", "d2"]).default("mermaid"))
265
+ .addOption(new Option("-f, --format <format>", "output format")
266
+ .choices(["ascii", "svg"]).default("ascii"))
292
267
  .option("-a, --ascii", "emit plain ASCII (+-|) instead of Unicode box-drawing", false)
293
- .option("-c, --color-mode <mode>", "force color mode (\"none\", \"ansi16\", or \"ansi256\") (Mermaid only)", parseColorMode("--color-mode"), Diagram.detectColorMode())
268
+ .addOption(new Option("-c, --color-mode <mode>", "force color mode (Mermaid only)")
269
+ .choices(["none", "ansi16", "ansi256"]).default(Diagram.detectColorMode()))
294
270
  .option("--node-margin-x <n>", "horizontal margin between nodes of <n> characters (Mermaid only)", parseInteger("--node-margin-x"), 3)
295
271
  .option("--node-margin-y <n>", "vertical margin between nodes of <n> lines (Mermaid only)", parseInteger("--node-margin-y"), 3)
296
272
  .option("--node-padding <n>", "horizontal and vertical inner node padding with <n> characters (Mermaid only)", parseInteger("--node-padding"), 1)
@@ -331,9 +307,7 @@ export default class DiagramCommand {
331
307
  await Diagram.dispose();
332
308
  }
333
309
  /* output diagram rendering */
334
- process.stdout.write(out);
335
- if (!out.endsWith("\n"))
336
- process.stdout.write("\n");
310
+ await writeStdout(out.endsWith("\n") ? out : `${out}\n`);
337
311
  });
338
312
  }
339
313
  }
package/dst/ase.js CHANGED
@@ -112,9 +112,9 @@ const main = async () => {
112
112
  the command line actually addresses; when no sub-command matches
113
113
  at all, all of them are registered, as only then Commander can
114
114
  render the complete sub-command overview of a help or a typo */
115
- const register = async (name, rest) => {
116
- const parent = await commands[name](program);
117
- const subs = subCommands[name];
115
+ const register = async (top) => {
116
+ const parent = await commands[top](program);
117
+ const subs = subCommands[top];
118
118
  if (subs === undefined || !(parent instanceof Command))
119
119
  return;
120
120
  const wanted = Object.keys(subs).filter((sub) => rest.includes(sub));
@@ -133,10 +133,10 @@ const main = async () => {
133
133
  answered by the program itself and hence needs no command module,
134
134
  while an unknown command and any help request need all of them */
135
135
  if (Object.hasOwn(commands, name))
136
- await register(name, rest);
136
+ await register(name);
137
137
  else if (!(name === "" && version))
138
138
  for (const command of Object.keys(commands))
139
- await register(command, rest);
139
+ await register(command);
140
140
  /* parse program arguments */
141
141
  await program.parseAsync(process.argv);
142
142
  /* gracefully terminate */
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-ADHOC" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-ADHOC" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-adhoc\fR - Run an Ad-Hoc Agent of a Definition Given by Options
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-CONDENSER" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-CONDENSER" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-condenser\fR - Plan Condensing Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-EDITOR" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-EDITOR" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-editor\fR - Edit Request Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-ESTIMATOR" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-ESTIMATOR" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-estimator\fR - Effort Estimation Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-FLEET" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-FLEET" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-fleet\fR - Manage a Fleet of Agents
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-IMPLEMENTER" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-IMPLEMENTER" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-implementer\fR - Implementation Phase Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-INTEGRATOR" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-INTEGRATOR" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-integrator\fR - Integration Phase Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-QUALIFIER" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-QUALIFIER" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-qualifier\fR - Qualification Phase Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-REBOOTER" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-REBOOTER" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-rebooter\fR - Plan Rebooting Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-RELAY" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-RELAY" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-relay\fR - Manage the Agent Relay Server
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT-RESEARCHER" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT-RESEARCHER" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent-researcher\fR - Research Agent
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-AGENT" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-AGENT" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-agent\fR - Autonomous Task Lifecycle Agents
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-ARTIFACT-LIST" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-ARTIFACT-LIST" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-artifact-list\fR - Resolve Artifact Kinds to Project-Relative File Paths
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-ARTIFACT-NAME" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-ARTIFACT-NAME" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-artifact-name\fR - Resolve a Filename within an Artifact Kind
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-ARTIFACT" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-ARTIFACT" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-artifact\fR - Resolve Project Artifact Kinds to File Lists
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG-DELETE" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG-DELETE" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config-delete\fR - Delete a Configuration Value
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG-EDIT" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG-EDIT" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config-edit\fR - Edit the Configuration File with an Editor
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG-GET" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG-GET" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config-get\fR - Print a Configuration Value
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG-INIT" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG-INIT" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config-init\fR - Initialize Configuration with Preset Values
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG-LIST" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG-LIST" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config-list\fR - List the Effective Configuration
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG-SET" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG-SET" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config-set\fR - Set a Configuration Value
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-CONFIG" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-CONFIG" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-config\fR - Manage the Layered ASE Configuration
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-HOOK-PERMISSION-REQUEST" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-HOOK-PERMISSION-REQUEST" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-hook-permission-request\fR - Handle the PermissionRequest Hook Event
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-HOOK-PRE-TOOL-USE" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-HOOK-PRE-TOOL-USE" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-hook-pre-tool-use\fR - Handle the PreToolUse Hook Event
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-HOOK-SESSION-END" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-HOOK-SESSION-END" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-hook-session-end\fR - Handle the SessionEnd Hook Event
4
4
  .SH "SYNOPSIS"
@@ -1,4 +1,4 @@
1
- .TH "ASE-HOOK-SESSION-PERSONA" "1" "October 2026" "2.0.1" "Agentic Software Engineering (ASE)"
1
+ .TH "ASE-HOOK-SESSION-PERSONA" "1" "October 2026" "2.0.3" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-hook-session-persona\fR - Handle the SessionStart Hook Event for the Persona
4
4
  .SH "SYNOPSIS"