@rse/ase 2.0.1 → 2.0.2

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 (119) hide show
  1. package/dst/ase-config.js +2 -1
  2. package/dst/ase-hook.js +2 -7
  3. package/dst/ase-lib-server-cli.js +6 -5
  4. package/dst/ase-task-board-core.js +1 -1
  5. package/dst/ase-task-board-tui-control.js +10 -10
  6. package/dst/ase-task-board-tui-model.js +10 -14
  7. package/dst/ase-task-board-tui-view.js +14 -9
  8. package/dst/ase-task-board-web-client.css +1 -1
  9. package/dst/ase-task-board-web-client.js +2 -2
  10. package/dst/ase-task-board-web-server.js +2 -25
  11. package/dst/ase-task-store-issues.js +76 -0
  12. package/dst/ase-task-store-plugin-gitea.js +2 -65
  13. package/dst/ase-task-store-plugin-github.js +2 -65
  14. package/dst/ase-task-store-plugin-gitlab.js +2 -65
  15. package/dst/ase-util-diagram.js +3 -5
  16. package/dst/man/ase-agent-adhoc.1 +1 -1
  17. package/dst/man/ase-agent-condenser.1 +1 -1
  18. package/dst/man/ase-agent-editor.1 +1 -1
  19. package/dst/man/ase-agent-estimator.1 +1 -1
  20. package/dst/man/ase-agent-fleet.1 +1 -1
  21. package/dst/man/ase-agent-implementer.1 +1 -1
  22. package/dst/man/ase-agent-integrator.1 +1 -1
  23. package/dst/man/ase-agent-qualifier.1 +1 -1
  24. package/dst/man/ase-agent-rebooter.1 +1 -1
  25. package/dst/man/ase-agent-relay.1 +1 -1
  26. package/dst/man/ase-agent-researcher.1 +1 -1
  27. package/dst/man/ase-agent.1 +1 -1
  28. package/dst/man/ase-artifact-list.1 +1 -1
  29. package/dst/man/ase-artifact-name.1 +1 -1
  30. package/dst/man/ase-artifact.1 +1 -1
  31. package/dst/man/ase-config-delete.1 +1 -1
  32. package/dst/man/ase-config-edit.1 +1 -1
  33. package/dst/man/ase-config-get.1 +1 -1
  34. package/dst/man/ase-config-init.1 +1 -1
  35. package/dst/man/ase-config-list.1 +1 -1
  36. package/dst/man/ase-config-set.1 +1 -1
  37. package/dst/man/ase-config.1 +1 -1
  38. package/dst/man/ase-hook-permission-request.1 +1 -1
  39. package/dst/man/ase-hook-pre-tool-use.1 +1 -1
  40. package/dst/man/ase-hook-session-end.1 +1 -1
  41. package/dst/man/ase-hook-session-persona.1 +1 -1
  42. package/dst/man/ase-hook-session-start.1 +1 -1
  43. package/dst/man/ase-hook-stop.1 +1 -1
  44. package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
  45. package/dst/man/ase-hook.1 +1 -1
  46. package/dst/man/ase-man.1 +1 -1
  47. package/dst/man/ase-mcp.1 +1 -1
  48. package/dst/man/ase-service-log.1 +1 -1
  49. package/dst/man/ase-service-send.1 +1 -1
  50. package/dst/man/ase-service-start.1 +1 -1
  51. package/dst/man/ase-service-status.1 +1 -1
  52. package/dst/man/ase-service-stop.1 +1 -1
  53. package/dst/man/ase-service.1 +1 -1
  54. package/dst/man/ase-setup-disable.1 +1 -1
  55. package/dst/man/ase-setup-enable.1 +1 -1
  56. package/dst/man/ase-setup-install.1 +1 -1
  57. package/dst/man/ase-setup-mcp.1 +1 -1
  58. package/dst/man/ase-setup-status.1 +1 -1
  59. package/dst/man/ase-setup-statusline.1 +1 -1
  60. package/dst/man/ase-setup-telemetry.1 +1 -1
  61. package/dst/man/ase-setup-uninstall.1 +1 -1
  62. package/dst/man/ase-setup-update.1 +1 -1
  63. package/dst/man/ase-setup.1 +1 -1
  64. package/dst/man/ase-spec-export.1 +1 -1
  65. package/dst/man/ase-spec-lint.1 +1 -1
  66. package/dst/man/ase-spec-preview.1 +1 -1
  67. package/dst/man/ase-spec.1 +1 -1
  68. package/dst/man/ase-statusline.1 +1 -1
  69. package/dst/man/ase-task-assign.1 +1 -1
  70. package/dst/man/ase-task-board.1 +3 -3
  71. package/dst/man/ase-task-delete.1 +1 -1
  72. package/dst/man/ase-task-edit.1 +1 -1
  73. package/dst/man/ase-task-idscheme.1 +1 -1
  74. package/dst/man/ase-task-lifecycle.1 +1 -1
  75. package/dst/man/ase-task-list.1 +1 -1
  76. package/dst/man/ase-task-load.1 +1 -1
  77. package/dst/man/ase-task-newid.1 +1 -1
  78. package/dst/man/ase-task-purge.1 +1 -1
  79. package/dst/man/ase-task-rename.1 +1 -1
  80. package/dst/man/ase-task-request.1 +1 -1
  81. package/dst/man/ase-task-save.1 +1 -1
  82. package/dst/man/ase-task-status.1 +1 -1
  83. package/dst/man/ase-task-store.1 +1 -1
  84. package/dst/man/ase-task-view.1 +1 -1
  85. package/dst/man/ase-task.1 +1 -1
  86. package/dst/man/ase-util-compat.1 +1 -1
  87. package/dst/man/ase-util-diagram.1 +1 -1
  88. package/dst/man/ase-util-meta.1 +1 -1
  89. package/dst/man/ase-util-metric.1 +1 -1
  90. package/dst/man/ase-util-mint.1 +1 -1
  91. package/dst/man/ase-util-worktree.1 +1 -1
  92. package/dst/man/ase-util.1 +1 -1
  93. package/dst/man/ase.1 +1 -1
  94. package/package.json +1 -1
  95. package/plugin/.claude-plugin/plugin.json +1 -1
  96. package/plugin/.codex-plugin/plugin.json +1 -1
  97. package/plugin/.github/plugin/plugin.json +1 -1
  98. package/plugin/meta/ase-common-resolve.md +2 -2
  99. package/plugin/mods/ase-mods.ts +6 -5
  100. package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
  101. package/plugin/skills/ase-code-analyze/SKILL.md +8 -0
  102. package/plugin/skills/ase-code-edit/SKILL.md +5 -4
  103. package/plugin/skills/ase-code-edit/help.md +1 -1
  104. package/plugin/skills/ase-code-lint/SKILL.md +15 -4
  105. package/plugin/skills/ase-help-intent/data.md +36 -18
  106. package/plugin/skills/ase-help-skill/help.md +3 -4
  107. package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
  108. package/plugin/skills/ase-meta-quorum/help.md +5 -3
  109. package/plugin/skills/ase-meta-workflow/sample.md +5 -3
  110. package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
  111. package/plugin/skills/ase-repo-changelog/help.md +2 -2
  112. package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
  113. package/plugin/skills/ase-spec-edit/help.md +2 -1
  114. package/plugin/skills/ase-sync-reconcile/help.md +4 -4
  115. package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
  116. package/plugin/skills/ase-task-grill/help.md +1 -1
  117. package/plugin/skills/ase-task-implement/SKILL.md +58 -1
  118. package/plugin/skills/ase-task-implement/help.md +16 -0
  119. package/plugin/skills/ase-task-list/help.md +2 -2
@@ -18,7 +18,7 @@ import { flowOf, layoutFlow, drawFlowSVG } from "./ase-task-board-flow.js";
18
18
  import { filterBoard, dropStandalone } from "./ase-task-board-filter.js";
19
19
  import { statsOf, statsRanges, effortScale } from "./ase-task-board-stats.js";
20
20
  import * as TaskFormat from "./ase-task-format.js";
21
- import { editProbe, editRefused, requestEdit, requestsOf, RequestError } from "./ase-task-request.js";
21
+ import { editProbe, editRefused, requestsOf, RequestError } from "./ase-task-request.js";
22
22
  import pkg from "../package.json" with { type: "json" };
23
23
  /* the build stamp of the loaded code (version and content hash of the board modules
24
24
  and page assets), reported by the ping route to let the CLI detect a stale running service
@@ -26,7 +26,7 @@ import pkg from "../package.json" with { type: "json" };
26
26
  export const BOARD_BUILD = (() => {
27
27
  const dir = path.dirname(fileURLToPath(import.meta.url));
28
28
  const hash = crypto.createHash("sha1");
29
- for (const f of ["core.js", "filter.js", "graph.js", "stats.js", "web-server.js", "web-client.html", "web-client.css", "web-client.js"])
29
+ for (const f of ["core.js", "filter.js", "flow.js", "graph.js", "stats.js", "web-server.js", "web-client.html", "web-client.css", "web-client.js"])
30
30
  hash.update(fs.readFileSync(path.join(dir, `ase-task-board-${f}`)));
31
31
  return `${pkg.version}:${hash.digest("hex")}`;
32
32
  })();
@@ -637,29 +637,6 @@ const registerUpdateRoutes = (server, log) => {
637
637
  return h.response({ prompt: probe.prompt });
638
638
  })
639
639
  });
640
- /* request the edit of a task by a prompt (processed by "ase agent editor"), answering
641
- 400 if the prompt is empty, 404 if the task is missing, and 409 if it is finished or
642
- an edit of it is in progress (as checked atomically with the request) */
643
- server.route({
644
- method: "POST",
645
- path: "/task-board/api/task/{id}/edit",
646
- options: { payload: { parse: true, allow: "application/json" } },
647
- handler: guarded(async (request, h) => {
648
- const id = String(request.params.id);
649
- const p = request.payload;
650
- if (!TaskFormat.TASK_ID_RE.test(id) || p === null || typeof p.prompt !== "string")
651
- return h.response({ error: "invalid edit request" }).code(400);
652
- try {
653
- await requestEdit(log, id, p.prompt);
654
- }
655
- catch (err) {
656
- const code = !(err instanceof RequestError) || err.reason === "empty" ? 400 :
657
- err.reason === "missing" ? 404 : 409;
658
- return h.response({ error: err instanceof Error ? err.message : String(err) }).code(code);
659
- }
660
- return h.response({ ok: true });
661
- })
662
- });
663
640
  /* request a task by a prompt under a request name (processed by a request agent of
664
641
  a definition, resp. by "ase agent editor" for "edit"), answering 400 if the
665
642
  request name is invalid or the prompt of an "edit" request is empty (as the prompts
@@ -235,3 +235,79 @@ export const attachmentSync = (current, wanted) => {
235
235
  }
236
236
  return ops;
237
237
  };
238
+ /* the common base of the issue tracker storage plugins: the projects mapped onto
239
+ their locations, the storage lifecycle polling for changes while opened and
240
+ observed (without keeping the process alive), and the project listing */
241
+ export class IssueTaskStoragePlugin {
242
+ ctx;
243
+ unit;
244
+ now;
245
+ url = "";
246
+ poll = 0;
247
+ repos = new Map();
248
+ listener = null;
249
+ timer = null;
250
+ opened;
251
+ polling = false;
252
+ polls = new Map();
253
+ constructor(ctx, unit, now) {
254
+ this.ctx = ctx;
255
+ this.unit = unit;
256
+ this.now = now;
257
+ this.opened = now();
258
+ }
259
+ /* the storage lifecycle */
260
+ async open() {
261
+ this.opened = this.now();
262
+ if (this.poll > 0 && this.listener !== null) {
263
+ this.timer = setInterval(() => {
264
+ this.pollAll().catch(() => { });
265
+ }, this.poll * 1000);
266
+ this.timer.unref();
267
+ }
268
+ this.ctx.log("debug", `opened ${this.repos.size} ${this.unit} on ${this.url} (polling every ${this.poll}s)`);
269
+ }
270
+ async close() {
271
+ if (this.timer !== null)
272
+ clearInterval(this.timer);
273
+ this.timer = null;
274
+ }
275
+ /* observe the changes of the issues made outside of this plugin instance */
276
+ watch(listener) {
277
+ this.listener = listener;
278
+ }
279
+ /* list the registered ones of the mapped projects */
280
+ async projectList() {
281
+ const out = [];
282
+ for (const prjId of this.repos.keys()) {
283
+ const entry = await this.projectGet(prjId);
284
+ if (entry !== null)
285
+ out.push(entry);
286
+ }
287
+ return out;
288
+ }
289
+ /* the change detection state of a project (starting at the opening time) */
290
+ pollState(prjId) {
291
+ let state = this.polls.get(prjId);
292
+ if (state === undefined) {
293
+ state = { since: this.opened, seen: new Map() };
294
+ this.polls.set(prjId, state);
295
+ }
296
+ return state;
297
+ }
298
+ /* poll all mapped projects, never overlapping itself */
299
+ async pollAll() {
300
+ if (this.polling)
301
+ return;
302
+ this.polling = true;
303
+ try {
304
+ for (const prjId of this.repos.keys())
305
+ await this.pollProject(prjId).catch((err) => {
306
+ this.ctx.log("warning", `polling project "${prjId}" failed: ${err instanceof Error ? err.message : String(err)}`);
307
+ });
308
+ }
309
+ finally {
310
+ this.polling = false;
311
+ }
312
+ }
313
+ }
@@ -50,19 +50,10 @@ const inRepo = (loc, issue) => issue.repository === undefined || issue.repositor
50
50
  "After" onto the dependencies (the blocking issues), "Created"/"Modified" onto the
51
51
  issue timestamps, any other key (including "Group", as Gitea has no parent issues)
52
52
  onto "ase:<key>:<value>" labels, and the attachments onto the issue comments */
53
- class GiteaTaskStoragePlugin {
54
- ctx;
53
+ class GiteaTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
55
54
  name = "gitea";
56
- url;
57
55
  source;
58
56
  gt;
59
- repos = new Map();
60
- poll;
61
- listener = null;
62
- timer = null;
63
- polling = false;
64
- opened = now();
65
- polls = new Map();
66
57
  registry = new LRUCache({ max: 64, ttl: 60 * 1000 });
67
58
  latest = new Map();
68
59
  labels = new Map();
@@ -71,7 +62,7 @@ class GiteaTaskStoragePlugin {
71
62
  depends = new LRUCache({ max: 1024, ttl: 60 * 1000 });
72
63
  loads = new LRUCache({ max: 256, ttl: 2 * 1000 });
73
64
  constructor(ctx) {
74
- this.ctx = ctx;
65
+ super(ctx, "repositories", now);
75
66
  const options = ctx.options;
76
67
  const token = options.token ?? process.env.GITEA_TOKEN ?? "";
77
68
  if (typeof token !== "string" || token === "")
@@ -147,27 +138,6 @@ class GiteaTaskStoragePlugin {
147
138
  return out;
148
139
  }
149
140
  }
150
- /* the storage lifecycle: polling for changes while opened and observed
151
- (without keeping the process alive) */
152
- async open() {
153
- this.opened = now();
154
- if (this.poll > 0 && this.listener !== null) {
155
- this.timer = setInterval(() => {
156
- this.pollAll().catch(() => { });
157
- }, this.poll * 1000);
158
- this.timer.unref();
159
- }
160
- this.ctx.log("debug", `opened ${this.repos.size} repositories on ${this.url} (polling every ${this.poll}s)`);
161
- }
162
- async close() {
163
- if (this.timer !== null)
164
- clearInterval(this.timer);
165
- this.timer = null;
166
- }
167
- /* observe the changes of the issues made outside of this plugin instance */
168
- watch(listener) {
169
- this.listener = listener;
170
- }
171
141
  /* ==== project registry ==== */
172
142
  /* the repository of a project */
173
143
  repo(prjId) {
@@ -240,15 +210,6 @@ class GiteaTaskStoragePlugin {
240
210
  async seqmark(loc, entry, fresh = false) {
241
211
  return Math.max(await this.latestNumber(loc, fresh), entry?.seqmark ?? 0);
242
212
  }
243
- async projectList() {
244
- const out = [];
245
- for (const prjId of this.repos.keys()) {
246
- const entry = await this.projectGet(prjId);
247
- if (entry !== null)
248
- out.push(entry);
249
- }
250
- return out;
251
- }
252
213
  /* get a project, with the sequence number high-water mark as the next
253
214
  allocated task id is the one of the next created issue */
254
215
  async projectGet(prjId) {
@@ -606,30 +567,6 @@ class GiteaTaskStoragePlugin {
606
567
  throw new Error(`task "${oldId}" cannot be renamed to "${newId}", as task ids are Gitea issue numbers`);
607
568
  }
608
569
  /* ==== change detection ==== */
609
- /* the change detection state of a project (starting at the opening time) */
610
- pollState(prjId) {
611
- let state = this.polls.get(prjId);
612
- if (state === undefined) {
613
- state = { since: this.opened, seen: new Map() };
614
- this.polls.set(prjId, state);
615
- }
616
- return state;
617
- }
618
- /* poll all registered projects, never overlapping itself */
619
- async pollAll() {
620
- if (this.polling)
621
- return;
622
- this.polling = true;
623
- try {
624
- for (const prjId of this.repos.keys())
625
- await this.pollProject(prjId).catch((err) => {
626
- this.ctx.log("warning", `polling project "${prjId}" failed: ${err instanceof Error ? err.message : String(err)}`);
627
- });
628
- }
629
- finally {
630
- this.polling = false;
631
- }
632
- }
633
570
  /* poll the issues of a project updated since the last poll (Gitea supports no
634
571
  conditional requests), and report the issues not seen at their update time yet:
635
572
  as added if created after the opening, else as updated (issues deleted on Gitea
@@ -60,18 +60,9 @@ const inRepo = (loc, issue) => {
60
60
  the attachments onto the issue comments (where "Group" and "After" fall back
61
61
  onto "ase:Group:<id>" resp. "ase:After:<ids>" labels on instances without
62
62
  sub-issues resp. issue dependencies) */
63
- class GitHubTaskStoragePlugin {
64
- ctx;
63
+ class GitHubTaskStoragePlugin extends TaskIssues.IssueTaskStoragePlugin {
65
64
  name = "github";
66
- url;
67
65
  gh;
68
- repos = new Map();
69
- poll;
70
- listener = null;
71
- timer = null;
72
- polling = false;
73
- opened = now();
74
- polls = new Map();
75
66
  registry = new LRUCache({ max: 64, ttl: 60 * 1000 });
76
67
  latest = new Map();
77
68
  labels = new Map();
@@ -80,7 +71,7 @@ class GitHubTaskStoragePlugin {
80
71
  recent = new Map();
81
72
  loads = new LRUCache({ max: 256, ttl: 2 * 1000 });
82
73
  constructor(ctx) {
83
- this.ctx = ctx;
74
+ super(ctx, "repositories", now);
84
75
  const options = ctx.options;
85
76
  const token = options.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? "";
86
77
  if (typeof token !== "string" || token === "")
@@ -144,27 +135,6 @@ class GitHubTaskStoragePlugin {
144
135
  throw err;
145
136
  });
146
137
  }
147
- /* the storage lifecycle: polling for changes while opened and observed
148
- (without keeping the process alive) */
149
- async open() {
150
- this.opened = now();
151
- if (this.poll > 0 && this.listener !== null) {
152
- this.timer = setInterval(() => {
153
- this.pollAll().catch(() => { });
154
- }, this.poll * 1000);
155
- this.timer.unref();
156
- }
157
- this.ctx.log("debug", `opened ${this.repos.size} repositories on ${this.url} (polling every ${this.poll}s)`);
158
- }
159
- async close() {
160
- if (this.timer !== null)
161
- clearInterval(this.timer);
162
- this.timer = null;
163
- }
164
- /* observe the changes of the issues made outside of this plugin instance */
165
- watch(listener) {
166
- this.listener = listener;
167
- }
168
138
  /* ==== project registry ==== */
169
139
  /* the repository of a project */
170
140
  repo(prjId) {
@@ -222,15 +192,6 @@ class GitHubTaskStoragePlugin {
222
192
  throw err;
223
193
  }
224
194
  }
225
- async projectList() {
226
- const out = [];
227
- for (const prjId of this.repos.keys()) {
228
- const entry = await this.projectGet(prjId);
229
- if (entry !== null)
230
- out.push(entry);
231
- }
232
- return out;
233
- }
234
195
  /* get a project, with the highest issue number as the sequence number high-water
235
196
  mark, so the next allocated task id is the one of the next created issue */
236
197
  async projectGet(prjId) {
@@ -618,30 +579,6 @@ class GitHubTaskStoragePlugin {
618
579
  throw new Error(`task "${oldId}" cannot be renamed to "${newId}", as task ids are GitHub issue numbers`);
619
580
  }
620
581
  /* ==== change detection ==== */
621
- /* the change detection state of a project (starting at the opening time) */
622
- pollState(prjId) {
623
- let state = this.polls.get(prjId);
624
- if (state === undefined) {
625
- state = { since: this.opened, seen: new Map() };
626
- this.polls.set(prjId, state);
627
- }
628
- return state;
629
- }
630
- /* poll all registered projects, never overlapping itself */
631
- async pollAll() {
632
- if (this.polling)
633
- return;
634
- this.polling = true;
635
- try {
636
- for (const prjId of this.repos.keys())
637
- await this.pollProject(prjId).catch((err) => {
638
- this.ctx.log("warning", `polling project "${prjId}" failed: ${err instanceof Error ? err.message : String(err)}`);
639
- });
640
- }
641
- finally {
642
- this.polling = false;
643
- }
644
- }
645
582
  /* poll the issues of a project updated since the last poll, conditionally
646
583
  via the entity tag of the last poll (a "304" answer is free of rate limit),
647
584
  and report the issues not seen at their update time yet: as added if created
@@ -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) {
@@ -596,30 +557,6 @@ class GitLabTaskStoragePlugin {
596
557
  throw new Error(`task "${oldId}" cannot be renamed to "${newId}", as task ids are GitLab issue numbers`);
597
558
  }
598
559
  /* ==== 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
560
  /* poll the issues of a project updated since the last poll, conditionally via
624
561
  the entity tag of the last poll (a "304" answer still counts against the rate
625
562
  limit of GitLab), and report the issues not seen at their update time yet: as
@@ -6,7 +6,7 @@
6
6
  import fs from "node:fs";
7
7
  import { InvalidArgumentError } 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);
@@ -115,7 +115,7 @@ const visibleWidth = (line) => {
115
115
  }
116
116
  return visible;
117
117
  };
118
- /* reusable functionality: Mermaid diagram rendering as Unicode/ASCII art */
118
+ /* reusable functionality: Mermaid and D2 diagram rendering as Unicode/ASCII art or SVG */
119
119
  export class Diagram {
120
120
  static detectTermDimension(envVar, stdoutProp) {
121
121
  let value = 0;
@@ -331,9 +331,7 @@ export default class DiagramCommand {
331
331
  await Diagram.dispose();
332
332
  }
333
333
  /* output diagram rendering */
334
- process.stdout.write(out);
335
- if (!out.endsWith("\n"))
336
- process.stdout.write("\n");
334
+ await writeStdout(out.endsWith("\n") ? out : `${out}\n`);
337
335
  });
338
336
  }
339
337
  }
@@ -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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "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.2" "Agentic Software Engineering (ASE)"
2
2
  .SH "NAME"
3
3
  \fBase-hook-session-end\fR - Handle the SessionEnd Hook Event
4
4
  .SH "SYNOPSIS"