@rse/ase 2.0.0 → 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.
- package/dst/ase-config-schema.js +7 -1
- package/dst/ase-config.js +2 -1
- package/dst/ase-hook.js +2 -7
- package/dst/ase-lib-server-cli.js +6 -5
- package/dst/ase-lib-version.js +8 -4
- package/dst/ase-service.js +19 -6
- package/dst/ase-task-board-core.js +1 -1
- package/dst/ase-task-board-tui-control.js +10 -10
- package/dst/ase-task-board-tui-model.js +10 -14
- package/dst/ase-task-board-tui-view.js +14 -9
- package/dst/ase-task-board-web-client.css +1 -1
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +2 -25
- package/dst/ase-task-store-issues.js +76 -0
- package/dst/ase-task-store-plugin-gitea.js +2 -65
- package/dst/ase-task-store-plugin-github.js +2 -65
- package/dst/ase-task-store-plugin-gitlab.js +2 -65
- package/dst/ase-util-diagram.js +3 -5
- package/dst/man/ase-agent-adhoc.1 +1 -1
- package/dst/man/ase-agent-condenser.1 +1 -1
- package/dst/man/ase-agent-editor.1 +1 -1
- package/dst/man/ase-agent-estimator.1 +1 -1
- package/dst/man/ase-agent-fleet.1 +1 -1
- package/dst/man/ase-agent-implementer.1 +1 -1
- package/dst/man/ase-agent-integrator.1 +1 -1
- package/dst/man/ase-agent-qualifier.1 +1 -1
- package/dst/man/ase-agent-rebooter.1 +1 -1
- package/dst/man/ase-agent-relay.1 +1 -1
- package/dst/man/ase-agent-researcher.1 +1 -1
- package/dst/man/ase-agent.1 +1 -1
- package/dst/man/ase-artifact-list.1 +1 -1
- package/dst/man/ase-artifact-name.1 +1 -1
- package/dst/man/ase-artifact.1 +1 -1
- package/dst/man/ase-config-delete.1 +1 -1
- package/dst/man/ase-config-edit.1 +1 -1
- package/dst/man/ase-config-get.1 +1 -1
- package/dst/man/ase-config-init.1 +1 -1
- package/dst/man/ase-config-list.1 +1 -1
- package/dst/man/ase-config-set.1 +1 -1
- package/dst/man/ase-config.1 +7 -1
- package/dst/man/ase-hook-permission-request.1 +1 -1
- package/dst/man/ase-hook-pre-tool-use.1 +1 -1
- package/dst/man/ase-hook-session-end.1 +1 -1
- package/dst/man/ase-hook-session-persona.1 +1 -1
- package/dst/man/ase-hook-session-start.1 +1 -1
- package/dst/man/ase-hook-stop.1 +1 -1
- package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
- package/dst/man/ase-hook.1 +1 -1
- package/dst/man/ase-man.1 +1 -1
- package/dst/man/ase-mcp.1 +1 -1
- package/dst/man/ase-service-log.1 +1 -1
- package/dst/man/ase-service-send.1 +1 -1
- package/dst/man/ase-service-start.1 +4 -4
- package/dst/man/ase-service-status.1 +1 -1
- package/dst/man/ase-service-stop.1 +1 -1
- package/dst/man/ase-service.1 +2 -2
- package/dst/man/ase-setup-disable.1 +1 -1
- package/dst/man/ase-setup-enable.1 +1 -1
- package/dst/man/ase-setup-install.1 +1 -1
- package/dst/man/ase-setup-mcp.1 +1 -1
- package/dst/man/ase-setup-status.1 +1 -1
- package/dst/man/ase-setup-statusline.1 +1 -1
- package/dst/man/ase-setup-telemetry.1 +1 -1
- package/dst/man/ase-setup-uninstall.1 +1 -1
- package/dst/man/ase-setup-update.1 +1 -1
- package/dst/man/ase-setup.1 +1 -1
- package/dst/man/ase-spec-export.1 +1 -1
- package/dst/man/ase-spec-lint.1 +1 -1
- package/dst/man/ase-spec-preview.1 +1 -1
- package/dst/man/ase-spec.1 +1 -1
- package/dst/man/ase-statusline.1 +1 -1
- package/dst/man/ase-task-assign.1 +1 -1
- package/dst/man/ase-task-board.1 +3 -3
- package/dst/man/ase-task-delete.1 +1 -1
- package/dst/man/ase-task-edit.1 +1 -1
- package/dst/man/ase-task-idscheme.1 +1 -1
- package/dst/man/ase-task-lifecycle.1 +1 -1
- package/dst/man/ase-task-list.1 +1 -1
- package/dst/man/ase-task-load.1 +1 -1
- package/dst/man/ase-task-newid.1 +1 -1
- package/dst/man/ase-task-purge.1 +1 -1
- package/dst/man/ase-task-rename.1 +1 -1
- package/dst/man/ase-task-request.1 +1 -1
- package/dst/man/ase-task-save.1 +1 -1
- package/dst/man/ase-task-status.1 +1 -1
- package/dst/man/ase-task-store.1 +1 -1
- package/dst/man/ase-task-view.1 +1 -1
- package/dst/man/ase-task.1 +1 -1
- package/dst/man/ase-util-compat.1 +1 -1
- package/dst/man/ase-util-diagram.1 +1 -1
- package/dst/man/ase-util-meta.1 +1 -1
- package/dst/man/ase-util-metric.1 +1 -1
- package/dst/man/ase-util-mint.1 +1 -1
- package/dst/man/ase-util-worktree.1 +1 -1
- package/dst/man/ase-util.1 +1 -1
- package/dst/man/ase.1 +1 -1
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/meta/ase-common-resolve.md +2 -2
- package/plugin/mods/ase-mods.ts +6 -5
- package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
- package/plugin/skills/ase-code-analyze/SKILL.md +8 -0
- package/plugin/skills/ase-code-edit/SKILL.md +5 -4
- package/plugin/skills/ase-code-edit/help.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +15 -4
- package/plugin/skills/ase-help-intent/data.md +36 -18
- package/plugin/skills/ase-help-skill/help.md +3 -4
- package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
- package/plugin/skills/ase-meta-quorum/help.md +5 -3
- package/plugin/skills/ase-meta-workflow/sample.md +5 -3
- package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
- package/plugin/skills/ase-repo-changelog/help.md +2 -2
- package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
- package/plugin/skills/ase-spec-edit/help.md +2 -1
- package/plugin/skills/ase-sync-reconcile/help.md +4 -4
- package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
- package/plugin/skills/ase-task-grill/help.md +1 -1
- package/plugin/skills/ase-task-implement/SKILL.md +58 -1
- package/plugin/skills/ase-task-implement/help.md +16 -0
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dst/ase-util-diagram.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
.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-IMPLEMENTER" "1" "October 2026" "2.0.
|
|
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"
|
package/dst/man/ase-agent.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "ASE-ARTIFACT-LIST" "1" "October 2026" "2.0.
|
|
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
|
+
.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"
|
package/dst/man/ase-artifact.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "ASE-CONFIG-EDIT" "1" "October 2026" "2.0.
|
|
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"
|
package/dst/man/ase-config-get.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "ASE-CONFIG-INIT" "1" "October 2026" "2.0.
|
|
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"
|
package/dst/man/ase-config-set.1
CHANGED
package/dst/man/ase-config.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "ASE-CONFIG" "1" "October 2026" "2.0.
|
|
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"
|
|
@@ -120,6 +120,12 @@ list\fR.
|
|
|
120
120
|
\fBproject.relay.crt\fR, \fBproject.relay.key\fR (\fIstring\fR): The PEM certificate (chain) and private key files of the agent relay server, relative to the directory of the configuration file defining them.
|
|
121
121
|
.RE 0
|
|
122
122
|
.P
|
|
123
|
+
Per-project service:
|
|
124
|
+
.RS 0
|
|
125
|
+
.IP \(bu 4
|
|
126
|
+
\fBproject.service.port\fR (\fIport\fR \fB1024\fR..\fB65535\fR, \fBproject\fR scope only): The preferred port of the per-project background service on \fB127.0.0.1\fR, falling back to a random port out of the range \fB42000\fR..\fB44000\fR if unavailable (default: none). See \fBase-service-start\fR(1).
|
|
127
|
+
.RE 0
|
|
128
|
+
.P
|
|
123
129
|
Project artifacts (all writable on the \fBuser\fR and \fBproject\fR scopes only), each kind defined by a \fBbasedir\fR directory (relative to the project root, empty for the root itself) and a whitespace-separated \fBfiles\fR glob specification (relative to \fBbasedir\fR, with \fB!\fR negating a glob). See \fBase-artifact\fR(1):
|
|
124
130
|
.RS 0
|
|
125
131
|
.IP \(bu 4
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "ASE-HOOK-PERMISSION-REQUEST" "1" "October 2026" "2.0.
|
|
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"
|