@rse/ase 0.9.11 → 0.9.12
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-compat.js +68 -0
- package/dst/ase-hook.js +158 -67
- package/dst/ase-service.js +2 -0
- package/dst/ase-setup.js +52 -23
- package/dst/ase-statusline.js +7 -3
- package/dst/ase.js +2 -0
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/mcp.json +8 -0
- package/plugin/.codex-plugin/plugin.json +17 -0
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/hooks/hooks-codex.json +67 -0
- package/plugin/meta/ase-dialog.md +20 -0
- package/plugin/package.json +1 -1
- package/plugin/skills/ase-code-craft/SKILL.md +1 -1
- package/plugin/skills/ase-code-refactor/SKILL.md +1 -1
- package/plugin/skills/ase-code-resolve/SKILL.md +1 -1
- package/plugin/skills/ase-meta-compat/SKILL.md +288 -0
- package/plugin/skills/ase-meta-compat/help.md +67 -0
- package/plugin/skills/ase-task-grill/SKILL.md +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Agentic Software Engineering (ASE)
|
|
3
|
+
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
|
+
*/
|
|
6
|
+
/* the canonical expected values for every ase-meta-compat probe,
|
|
7
|
+
keyed by "<category>/<probe-name>" as used in the skill */
|
|
8
|
+
const EXPECTED = {
|
|
9
|
+
/* xml-placeholders */
|
|
10
|
+
"xml-placeholders/get-and-set": "42",
|
|
11
|
+
"xml-placeholders/self-ref": "pre_42",
|
|
12
|
+
"xml-placeholders/overwrite": "99",
|
|
13
|
+
"xml-placeholders/indexed": "+1,-1",
|
|
14
|
+
"xml-placeholders/nested-attr": "20",
|
|
15
|
+
"xml-placeholders/entity": "○",
|
|
16
|
+
/* control-flow */
|
|
17
|
+
"control-flow/branch": "mid",
|
|
18
|
+
"control-flow/while-sum": "15",
|
|
19
|
+
"control-flow/for-order": "-x-y-z",
|
|
20
|
+
"control-flow/while-break": "4",
|
|
21
|
+
"control-flow/step-skip": "X",
|
|
22
|
+
"control-flow/expand-subst": "[K:V]",
|
|
23
|
+
/* regex */
|
|
24
|
+
"regex/getopt-dash": "yes",
|
|
25
|
+
"regex/getopt-nodash": "no",
|
|
26
|
+
"regex/anchored-int": "yes,no",
|
|
27
|
+
"regex/alternation": "yes",
|
|
28
|
+
"regex/capture": "foo",
|
|
29
|
+
"regex/whitespace": "yes",
|
|
30
|
+
"regex/complex": "yes",
|
|
31
|
+
/* arithmetic */
|
|
32
|
+
"arithmetic/increment": "9",
|
|
33
|
+
"arithmetic/product-sum": "5.00",
|
|
34
|
+
"arithmetic/percentage": "0.43",
|
|
35
|
+
"arithmetic/bar-width": "54",
|
|
36
|
+
"arithmetic/threshold": "yes",
|
|
37
|
+
"arithmetic/round-half": "3"
|
|
38
|
+
};
|
|
39
|
+
/* format the expected values as "<id>: <value>\n" lines */
|
|
40
|
+
const formatExpected = () => Object.entries(EXPECTED).map(([id, value]) => `${id}: ${value}`).join("\n") + "\n";
|
|
41
|
+
/* CLI command "ase compat" */
|
|
42
|
+
export default class CompatCommand {
|
|
43
|
+
register(program) {
|
|
44
|
+
program
|
|
45
|
+
.command("compat")
|
|
46
|
+
.description("Output expected probe values for the ase-meta-compat self-test skill")
|
|
47
|
+
.action(() => {
|
|
48
|
+
process.stdout.write(formatExpected());
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/* MCP tool "ase_compat" — lets the skill call this without a Bash tool */
|
|
53
|
+
export class CompatMCP {
|
|
54
|
+
register(mcp) {
|
|
55
|
+
mcp.registerTool("ase_compat", {
|
|
56
|
+
title: "ASE compat expected values",
|
|
57
|
+
description: "Return the canonical expected probe values for the ase-meta-compat " +
|
|
58
|
+
"self-test skill as \"<id>: <value>\" lines, one per probe. " +
|
|
59
|
+
"Call this AFTER recording all actual probe results so the LLM " +
|
|
60
|
+
"cannot see the expected values before running the probes.",
|
|
61
|
+
inputSchema: {}
|
|
62
|
+
}, async () => {
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: formatExpected() }]
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dst/ase-hook.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import { execaSync } from "execa";
|
|
10
10
|
import { quote } from "shell-quote";
|
|
11
|
+
import getStdin from "get-stdin";
|
|
11
12
|
import * as v from "valibot";
|
|
12
13
|
import Version from "./ase-version.js";
|
|
13
14
|
import { Config, configSchema, parseScope } from "./ase-config.js";
|
|
@@ -41,7 +42,8 @@ const toolSpecs = {
|
|
|
41
42
|
mcpToolNamePattern: /^mcp__plugin_ase_ase__.+/,
|
|
42
43
|
addonMcpToolNamePattern: addonMcpToolNamePattern("mcp__", "__.+"),
|
|
43
44
|
preToolUseWrapped: true,
|
|
44
|
-
preToolUseEvent: "PreToolUse"
|
|
45
|
+
preToolUseEvent: "PreToolUse",
|
|
46
|
+
approvalEvent: "PreToolUse"
|
|
45
47
|
},
|
|
46
48
|
"copilot": {
|
|
47
49
|
toolNameField: "toolName",
|
|
@@ -51,7 +53,19 @@ const toolSpecs = {
|
|
|
51
53
|
mcpToolNamePattern: /^ase-.+/,
|
|
52
54
|
addonMcpToolNamePattern: addonMcpToolNamePattern("", "-.+"),
|
|
53
55
|
preToolUseWrapped: false,
|
|
54
|
-
preToolUseEvent: "preToolUse"
|
|
56
|
+
preToolUseEvent: "preToolUse",
|
|
57
|
+
approvalEvent: "PreToolUse"
|
|
58
|
+
},
|
|
59
|
+
"codex": {
|
|
60
|
+
toolNameField: "tool_name",
|
|
61
|
+
toolInputField: "tool_input",
|
|
62
|
+
toolInputIsString: false,
|
|
63
|
+
bashToolName: "Bash",
|
|
64
|
+
mcpToolNamePattern: /^mcp__ase__.+/,
|
|
65
|
+
addonMcpToolNamePattern: addonMcpToolNamePattern("mcp__", "__.+"),
|
|
66
|
+
preToolUseWrapped: true,
|
|
67
|
+
preToolUseEvent: "PreToolUse",
|
|
68
|
+
approvalEvent: "PermissionRequest"
|
|
55
69
|
}
|
|
56
70
|
};
|
|
57
71
|
/* CLI command "ase hook" */
|
|
@@ -64,6 +78,27 @@ export default class HookCommand {
|
|
|
64
78
|
isValidSessionId(id) {
|
|
65
79
|
return /^[A-Za-z0-9._-]+$/.test(id);
|
|
66
80
|
}
|
|
81
|
+
/* read the entire stdin payload asynchronously */
|
|
82
|
+
readStdin() {
|
|
83
|
+
/* best-effort: treat an unreadable/closed stdin as empty */
|
|
84
|
+
return getStdin().catch(() => "");
|
|
85
|
+
}
|
|
86
|
+
/* drain and discard the stdin event payload */
|
|
87
|
+
async drainStdin() {
|
|
88
|
+
await this.readStdin();
|
|
89
|
+
}
|
|
90
|
+
/* write to stdout and resolve only once it has been fully
|
|
91
|
+
flushed to the underlying file descriptor */
|
|
92
|
+
writeStdout(text) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
process.stdout.write(text, (err) => {
|
|
95
|
+
if (err)
|
|
96
|
+
reject(err);
|
|
97
|
+
else
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
67
102
|
/* best-effort JSON parse with valibot schema validation: returns
|
|
68
103
|
an empty object on blank input, malformed JSON, or schema
|
|
69
104
|
mismatch, so callers can treat the result uniformly. Extra
|
|
@@ -110,13 +145,24 @@ export default class HookCommand {
|
|
|
110
145
|
return this.expandReferences(content, path.dirname(abs), next);
|
|
111
146
|
});
|
|
112
147
|
}
|
|
113
|
-
/* handler for "ase hook session-start" (
|
|
148
|
+
/* handler for "ase hook session-start" (all tools) */
|
|
114
149
|
async doSessionStart(tool) {
|
|
115
150
|
/* determine plugin root (env var name differs per tool) */
|
|
116
|
-
|
|
117
|
-
|
|
151
|
+
let pluginRootVars;
|
|
152
|
+
if (tool === "copilot")
|
|
153
|
+
pluginRootVars = ["COPILOT_PLUGIN_ROOT"];
|
|
154
|
+
else if (tool === "codex")
|
|
155
|
+
pluginRootVars = ["PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT"];
|
|
156
|
+
else
|
|
157
|
+
pluginRootVars = ["CLAUDE_PLUGIN_ROOT"];
|
|
158
|
+
let pluginRoot = "";
|
|
159
|
+
for (const pluginRootVar of pluginRootVars)
|
|
160
|
+
if ((process.env[pluginRootVar] ?? "") !== "") {
|
|
161
|
+
pluginRoot = process.env[pluginRootVar];
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
118
164
|
if (pluginRoot === "")
|
|
119
|
-
throw new Error(`${
|
|
165
|
+
throw new Error(`${pluginRootVars.join("/")} environment variable is not set`);
|
|
120
166
|
/* determine path to external files */
|
|
121
167
|
const filePkg = path.join(pluginRoot, ".claude-plugin", "plugin.json");
|
|
122
168
|
const fileMd = path.join(pluginRoot, "meta", "ase-constitution.md");
|
|
@@ -152,7 +198,7 @@ export default class HookCommand {
|
|
|
152
198
|
const versionHint = versionHints.length > 0 ? "(" + versionHints.join(", ") + ")" : "";
|
|
153
199
|
/* read session information (Claude Code uses snake_case fields,
|
|
154
200
|
Copilot CLI uses camelCase fields) */
|
|
155
|
-
const stdin =
|
|
201
|
+
const stdin = await this.readStdin();
|
|
156
202
|
const input = this.parseJSON(stdin, v.object({
|
|
157
203
|
session_id: v.optional(v.string()),
|
|
158
204
|
sessionId: v.optional(v.string()),
|
|
@@ -228,9 +274,10 @@ export default class HookCommand {
|
|
|
228
274
|
/* expand all @<file> references manually */
|
|
229
275
|
md = this.expandReferences(md, path.dirname(fileMd));
|
|
230
276
|
/* inject markdown into session context.
|
|
231
|
-
Claude Code
|
|
232
|
-
Copilot CLI expects a flat top-level
|
|
233
|
-
|
|
277
|
+
Claude Code and OpenAI Codex CLI expect the context nested in
|
|
278
|
+
"hookSpecificOutput"; Copilot CLI expects a flat top-level
|
|
279
|
+
"additionalContext" field. */
|
|
280
|
+
const payload = tool !== "copilot" ? {
|
|
234
281
|
"hookSpecificOutput": {
|
|
235
282
|
"hookEventName": "SessionStart",
|
|
236
283
|
"additionalContext": md
|
|
@@ -238,7 +285,7 @@ export default class HookCommand {
|
|
|
238
285
|
} : {
|
|
239
286
|
"additionalContext": md
|
|
240
287
|
};
|
|
241
|
-
|
|
288
|
+
await this.writeStdout(JSON.stringify(payload));
|
|
242
289
|
return 0;
|
|
243
290
|
}
|
|
244
291
|
/* publish the agent activity marker to tmux as a per-pane user
|
|
@@ -258,19 +305,21 @@ export default class HookCommand {
|
|
|
258
305
|
}
|
|
259
306
|
}
|
|
260
307
|
/* handler for "ase hook user-prompt-submit" (both tools) */
|
|
261
|
-
doUserPromptSubmit(_tool) {
|
|
308
|
+
async doUserPromptSubmit(_tool) {
|
|
309
|
+
await this.drainStdin();
|
|
262
310
|
this.writeAgentStatus("busy");
|
|
263
311
|
return 0;
|
|
264
312
|
}
|
|
265
313
|
/* handler for "ase hook stop" (both tools) */
|
|
266
|
-
doStop(_tool) {
|
|
314
|
+
async doStop(_tool) {
|
|
315
|
+
await this.drainStdin();
|
|
267
316
|
this.writeAgentStatus("ready");
|
|
268
317
|
return 0;
|
|
269
318
|
}
|
|
270
319
|
/* handler for "ase hook session-end" (both tools) */
|
|
271
|
-
doSessionEnd(_tool) {
|
|
320
|
+
async doSessionEnd(_tool) {
|
|
272
321
|
/* determine session id */
|
|
273
|
-
const sessionId = this.readSessionIdFromStdin();
|
|
322
|
+
const sessionId = await this.readSessionIdFromStdin();
|
|
274
323
|
/* remove the session directory ~/.ase/session/<id> (only for a valid sessionId) */
|
|
275
324
|
if (this.isValidSessionId(sessionId)) {
|
|
276
325
|
const dir = path.join(os.homedir(), ".ase", "session", sessionId);
|
|
@@ -289,8 +338,8 @@ export default class HookCommand {
|
|
|
289
338
|
return input.session_id ?? input.sessionId ?? "";
|
|
290
339
|
}
|
|
291
340
|
/* read session id from stdin JSON payload */
|
|
292
|
-
readSessionIdFromStdin() {
|
|
293
|
-
const stdin =
|
|
341
|
+
async readSessionIdFromStdin() {
|
|
342
|
+
const stdin = await this.readStdin();
|
|
294
343
|
const input = this.parseJSON(stdin, v.object({
|
|
295
344
|
session_id: v.optional(v.string()),
|
|
296
345
|
sessionId: v.optional(v.string())
|
|
@@ -319,17 +368,12 @@ export default class HookCommand {
|
|
|
319
368
|
/* the edit-capable skills whose active state lets the pre-tool-use
|
|
320
369
|
hook auto-approve subsequent "Edit" invocations */
|
|
321
370
|
editCapableSkills = ["ase-code-lint", "ase-docs-proofread"];
|
|
322
|
-
/*
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
session_id: v.optional(v.string()),
|
|
329
|
-
sessionId: v.optional(v.string())
|
|
330
|
-
}));
|
|
331
|
-
/* determine whether to auto-approve the tool invocation
|
|
332
|
-
(field names and value shapes differ between tools) */
|
|
371
|
+
/* determine whether an ASE tool invocation described by the parsed
|
|
372
|
+
hook input should be auto-approved, and (if so) the human-readable
|
|
373
|
+
reason. The input field names and value shapes differ between
|
|
374
|
+
tools, but the decision logic is shared by the "pre-tool-use" and
|
|
375
|
+
"permission-request" handlers. */
|
|
376
|
+
decideApproval(spec, input) {
|
|
333
377
|
const toolName = typeof input[spec.toolNameField] === "string" ?
|
|
334
378
|
input[spec.toolNameField] : "";
|
|
335
379
|
let toolInput = {};
|
|
@@ -341,32 +385,43 @@ export default class HookCommand {
|
|
|
341
385
|
}));
|
|
342
386
|
else if (!spec.toolInputIsString && typeof rawInput === "object" && rawInput !== null)
|
|
343
387
|
toolInput = rawInput;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (toolName ===
|
|
347
|
-
approve
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
else if (
|
|
351
|
-
approve
|
|
352
|
-
reason = "ASE skill invocation auto-approved";
|
|
353
|
-
}
|
|
354
|
-
else if (spec.mcpToolNamePattern.test(toolName)) {
|
|
355
|
-
approve = true;
|
|
356
|
-
reason = "ASE MCP tool invocation auto-approved";
|
|
357
|
-
}
|
|
358
|
-
else if (spec.addonMcpToolNamePattern.test(toolName)) {
|
|
359
|
-
approve = true;
|
|
360
|
-
reason = "ASE addon MCP tool invocation auto-approved";
|
|
361
|
-
}
|
|
388
|
+
if (toolName === spec.bashToolName && /^ase(\s|$)/.test(toolInput.command ?? ""))
|
|
389
|
+
return { approve: true, reason: "ASE CLI invocation auto-approved" };
|
|
390
|
+
else if (toolName === "Skill" && /^(?:ase:)?ase-.+/.test(toolInput.skill ?? ""))
|
|
391
|
+
return { approve: true, reason: "ASE skill invocation auto-approved" };
|
|
392
|
+
else if (spec.mcpToolNamePattern.test(toolName))
|
|
393
|
+
return { approve: true, reason: "ASE MCP tool invocation auto-approved" };
|
|
394
|
+
else if (spec.addonMcpToolNamePattern.test(toolName))
|
|
395
|
+
return { approve: true, reason: "ASE addon MCP tool invocation auto-approved" };
|
|
362
396
|
else if (toolName === "Edit") {
|
|
363
397
|
const sessionId = this.pickSessionId(input);
|
|
364
398
|
const activeSkill = this.readActiveSkill(sessionId);
|
|
365
|
-
if (this.editCapableSkills.includes(activeSkill))
|
|
366
|
-
approve
|
|
367
|
-
reason = `${activeSkill}: edit auto-approved for active edit-capable skill`;
|
|
368
|
-
}
|
|
399
|
+
if (this.editCapableSkills.includes(activeSkill))
|
|
400
|
+
return { approve: true, reason: `${activeSkill}: edit auto-approved for active edit-capable skill` };
|
|
369
401
|
}
|
|
402
|
+
return { approve: false, reason: "" };
|
|
403
|
+
}
|
|
404
|
+
/* handler for "ase hook pre-tool-use" (all tools).
|
|
405
|
+
For Claude Code and Copilot CLI this is where ASE tool
|
|
406
|
+
invocations are auto-approved (via "permissionDecision: allow").
|
|
407
|
+
OpenAI Codex CLI rejects that mechanism in "PreToolUse", so for
|
|
408
|
+
Codex this handler stays silent and approval is granted in the
|
|
409
|
+
separate "permission-request" handler instead -- the handler must
|
|
410
|
+
still drain stdin, as Codex treats a non-draining hook as a hard
|
|
411
|
+
error. */
|
|
412
|
+
async doPreToolUse(tool) {
|
|
413
|
+
const spec = toolSpecs[tool];
|
|
414
|
+
/* read tool invocation information */
|
|
415
|
+
const stdin = await this.readStdin();
|
|
416
|
+
const input = this.parseJSON(stdin, v.looseObject({
|
|
417
|
+
session_id: v.optional(v.string()),
|
|
418
|
+
sessionId: v.optional(v.string())
|
|
419
|
+
}));
|
|
420
|
+
/* Codex auto-approves through "PermissionRequest", not here */
|
|
421
|
+
if (spec.approvalEvent !== "PreToolUse")
|
|
422
|
+
return 0;
|
|
423
|
+
/* determine whether to auto-approve the tool invocation */
|
|
424
|
+
const { approve, reason } = this.decideApproval(spec, input);
|
|
370
425
|
/* emit permission decision (or stay silent to defer to default flow).
|
|
371
426
|
Claude Code expects the decision nested in "hookSpecificOutput";
|
|
372
427
|
Copilot CLI expects flat top-level fields. */
|
|
@@ -381,14 +436,42 @@ export default class HookCommand {
|
|
|
381
436
|
"permissionDecision": "allow",
|
|
382
437
|
"permissionDecisionReason": reason
|
|
383
438
|
};
|
|
384
|
-
|
|
439
|
+
await this.writeStdout(JSON.stringify(payload));
|
|
440
|
+
}
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
/* handler for "ase hook permission-request" (OpenAI Codex CLI only).
|
|
444
|
+
Codex fires this event only when a tool invocation would otherwise
|
|
445
|
+
require interactive user approval, and -- unlike "PreToolUse" --
|
|
446
|
+
honors an auto-approval here through "decision.behavior: allow".
|
|
447
|
+
Staying silent (or returning a non-approval) defers to Codex's
|
|
448
|
+
normal approval flow. */
|
|
449
|
+
async doPermissionRequest(tool) {
|
|
450
|
+
const spec = toolSpecs[tool];
|
|
451
|
+
/* read tool invocation information */
|
|
452
|
+
const stdin = await this.readStdin();
|
|
453
|
+
const input = this.parseJSON(stdin, v.looseObject({
|
|
454
|
+
session_id: v.optional(v.string()),
|
|
455
|
+
sessionId: v.optional(v.string())
|
|
456
|
+
}));
|
|
457
|
+
/* determine whether to auto-approve the tool invocation */
|
|
458
|
+
const { approve } = this.decideApproval(spec, input);
|
|
459
|
+
/* emit the Codex "PermissionRequest" approval decision */
|
|
460
|
+
if (approve) {
|
|
461
|
+
const payload = {
|
|
462
|
+
"hookSpecificOutput": {
|
|
463
|
+
"hookEventName": "PermissionRequest",
|
|
464
|
+
"decision": { "behavior": "allow" }
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
await this.writeStdout(JSON.stringify(payload));
|
|
385
468
|
}
|
|
386
469
|
return 0;
|
|
387
470
|
}
|
|
388
471
|
/* parse and validate the --tool option */
|
|
389
472
|
parseTool(value) {
|
|
390
|
-
if (value !== "claude" && value !== "copilot")
|
|
391
|
-
throw new Error(`invalid --tool value: "${value}" (expected "claude" or "
|
|
473
|
+
if (value !== "claude" && value !== "copilot" && value !== "codex")
|
|
474
|
+
throw new Error(`invalid --tool value: "${value}" (expected "claude", "copilot", or "codex")`);
|
|
392
475
|
return value;
|
|
393
476
|
}
|
|
394
477
|
/* register commands */
|
|
@@ -408,41 +491,49 @@ export default class HookCommand {
|
|
|
408
491
|
hookCmd
|
|
409
492
|
.command("session-start")
|
|
410
493
|
.description("handle SessionStart hook event")
|
|
411
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
494
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
412
495
|
.action(async (opts) => {
|
|
413
|
-
process.
|
|
496
|
+
process.exitCode = await this.doSessionStart(this.parseTool(opts.tool));
|
|
414
497
|
});
|
|
415
498
|
/* register CLI sub-command "ase hook session-end" */
|
|
416
499
|
hookCmd
|
|
417
500
|
.command("session-end")
|
|
418
501
|
.description("handle SessionEnd hook event")
|
|
419
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
420
|
-
.action((opts) => {
|
|
421
|
-
process.
|
|
502
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
503
|
+
.action(async (opts) => {
|
|
504
|
+
process.exitCode = await this.doSessionEnd(this.parseTool(opts.tool));
|
|
422
505
|
});
|
|
423
506
|
/* register CLI sub-command "ase hook pre-tool-use" */
|
|
424
507
|
hookCmd
|
|
425
508
|
.command("pre-tool-use")
|
|
426
509
|
.description("handle tool PreToolUse hook event")
|
|
427
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
428
|
-
.action((opts) => {
|
|
429
|
-
process.
|
|
510
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
511
|
+
.action(async (opts) => {
|
|
512
|
+
process.exitCode = await this.doPreToolUse(this.parseTool(opts.tool));
|
|
513
|
+
});
|
|
514
|
+
/* register CLI sub-command "ase hook permission-request" */
|
|
515
|
+
hookCmd
|
|
516
|
+
.command("permission-request")
|
|
517
|
+
.description("handle tool PermissionRequest hook event (Codex CLI)")
|
|
518
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
519
|
+
.action(async (opts) => {
|
|
520
|
+
process.exitCode = await this.doPermissionRequest(this.parseTool(opts.tool));
|
|
430
521
|
});
|
|
431
522
|
/* register CLI sub-command "ase hook user-prompt-submit" */
|
|
432
523
|
hookCmd
|
|
433
524
|
.command("user-prompt-submit")
|
|
434
525
|
.description("handle UserPromptSubmit hook event (mark agent as busy)")
|
|
435
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
436
|
-
.action((opts) => {
|
|
437
|
-
process.
|
|
526
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
527
|
+
.action(async (opts) => {
|
|
528
|
+
process.exitCode = await this.doUserPromptSubmit(this.parseTool(opts.tool));
|
|
438
529
|
});
|
|
439
530
|
/* register CLI sub-command "ase hook stop" */
|
|
440
531
|
hookCmd
|
|
441
532
|
.command("stop")
|
|
442
533
|
.description("handle Stop hook event (mark agent as ready)")
|
|
443
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
444
|
-
.action((opts) => {
|
|
445
|
-
process.
|
|
534
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
535
|
+
.action(async (opts) => {
|
|
536
|
+
process.exitCode = await this.doStop(this.parseTool(opts.tool));
|
|
446
537
|
});
|
|
447
538
|
}
|
|
448
539
|
}
|
package/dst/ase-service.js
CHANGED
|
@@ -16,6 +16,7 @@ import * as v from "valibot";
|
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
17
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
18
18
|
import { Config, configSchema, ConfigMCP } from "./ase-config.js";
|
|
19
|
+
import { CompatMCP } from "./ase-compat.js";
|
|
19
20
|
import { DiagramMCP } from "./ase-diagram.js";
|
|
20
21
|
import { TaskMCP } from "./ase-task.js";
|
|
21
22
|
import { MarkdownMCP } from "./ase-markdown.js";
|
|
@@ -236,6 +237,7 @@ export default class ServiceCommand {
|
|
|
236
237
|
const buildMcpServer = () => {
|
|
237
238
|
const mcp = new McpServer({ name: "ase", version: pkg.version });
|
|
238
239
|
new ServiceMCP({ projectId: ctx.projectId, port: ctx.port, startTime }).register(mcp);
|
|
240
|
+
new CompatMCP().register(mcp);
|
|
239
241
|
new DiagramMCP().register(mcp);
|
|
240
242
|
new TaskMCP(this.log).register(mcp);
|
|
241
243
|
new MarkdownMCP().register(mcp);
|
package/dst/ase-setup.js
CHANGED
|
@@ -13,8 +13,9 @@ import Table from "cli-table3";
|
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import Version from "./ase-version.js";
|
|
15
15
|
const toolSpecs = {
|
|
16
|
-
"claude": { cli: "claude", label: "Claude Code" },
|
|
17
|
-
"copilot": { cli: "copilot", label: "Copilot CLI" }
|
|
16
|
+
"claude": { cli: "claude", label: "Claude Code", pInstall: "install", pRemove: "uninstall", pUpdate: "update" },
|
|
17
|
+
"copilot": { cli: "copilot", label: "Copilot CLI", pInstall: "install", pRemove: "uninstall", pUpdate: "update" },
|
|
18
|
+
"codex": { cli: "codex", label: "OpenAI Codex CLI", pInstall: "add", pRemove: "remove", pUpdate: "upgrade" }
|
|
18
19
|
};
|
|
19
20
|
/* CLI command "ase setup" */
|
|
20
21
|
export default class SetupCommand {
|
|
@@ -162,7 +163,7 @@ export default class SetupCommand {
|
|
|
162
163
|
const pkgdir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
163
164
|
const source = dev ? path.resolve(pkgdir, "..") : pkgdir;
|
|
164
165
|
await this.run(spec.cli, ["plugin", "marketplace", "add", source]);
|
|
165
|
-
await this.run(spec.cli, ["plugin",
|
|
166
|
+
await this.run(spec.cli, ["plugin", spec.pInstall, "ase@ase"], { retries: 3 });
|
|
166
167
|
return 0;
|
|
167
168
|
}
|
|
168
169
|
/* handler for "ase setup update" (both tools) */
|
|
@@ -185,8 +186,8 @@ export default class SetupCommand {
|
|
|
185
186
|
but there is no version change in the plugin manifest,
|
|
186
187
|
so just re-install the plugin to let the tool update its copy */
|
|
187
188
|
this.log.write("info", `setup: update[dev]: re-install ASE ${spec.label} plugin (origin: local)`);
|
|
188
|
-
await this.run(spec.cli, ["plugin",
|
|
189
|
-
await this.run(spec.cli, ["plugin",
|
|
189
|
+
await this.run(spec.cli, ["plugin", spec.pRemove, "ase@ase"], { ignoreError: `ASE ${spec.label} plugin not installed` });
|
|
190
|
+
await this.run(spec.cli, ["plugin", spec.pInstall, "ase@ase"], { retries: 3 });
|
|
190
191
|
}
|
|
191
192
|
else {
|
|
192
193
|
/* perform NPM version check */
|
|
@@ -201,10 +202,17 @@ export default class SetupCommand {
|
|
|
201
202
|
this.log.write("info", `setup: update: updating ASE CLI tool: ${current} -> ${latest}`);
|
|
202
203
|
const updateCmd = await this.npmCmd(["update", "-g", "@rse/ase"], true);
|
|
203
204
|
await this.run(updateCmd.cmd, updateCmd.args);
|
|
204
|
-
/* update ASE plugin
|
|
205
|
+
/* update ASE plugin (refresh the marketplace snapshot, then
|
|
206
|
+
update the plugin itself; the OpenAI Codex CLI has no
|
|
207
|
+
"plugin update" subcommand, so re-install it instead) */
|
|
205
208
|
this.log.write("info", `setup: update: updating ASE ${spec.label} plugin`);
|
|
206
|
-
await this.run(spec.cli, ["plugin", "marketplace",
|
|
207
|
-
|
|
209
|
+
await this.run(spec.cli, ["plugin", "marketplace", spec.pUpdate, "ase"]);
|
|
210
|
+
if (tool !== "codex")
|
|
211
|
+
await this.run(spec.cli, ["plugin", "update", "ase@ase"]);
|
|
212
|
+
else {
|
|
213
|
+
await this.run(spec.cli, ["plugin", spec.pRemove, "ase@ase"], { ignoreError: `ASE ${spec.label} plugin not installed` });
|
|
214
|
+
await this.run(spec.cli, ["plugin", spec.pInstall, "ase@ase"], { retries: 3 });
|
|
215
|
+
}
|
|
208
216
|
}
|
|
209
217
|
return 0;
|
|
210
218
|
}
|
|
@@ -213,9 +221,11 @@ export default class SetupCommand {
|
|
|
213
221
|
const spec = toolSpecs[tool];
|
|
214
222
|
await this.ensureTool(spec.cli);
|
|
215
223
|
this.log.write("info", `setup: enable: enabling ASE ${spec.label} plugin`);
|
|
224
|
+
/* the GitHub Copilot CLI and OpenAI Codex CLI have no "plugin
|
|
225
|
+
enable" subcommand, so (re-)install the plugin instead */
|
|
216
226
|
const args = tool === "claude" ?
|
|
217
227
|
["plugin", "enable", "ase@ase"] :
|
|
218
|
-
["plugin",
|
|
228
|
+
["plugin", spec.pInstall, "ase@ase"];
|
|
219
229
|
await this.run(spec.cli, args, { retries: tool === "claude" ? 1 : 3 });
|
|
220
230
|
return 0;
|
|
221
231
|
}
|
|
@@ -224,9 +234,11 @@ export default class SetupCommand {
|
|
|
224
234
|
const spec = toolSpecs[tool];
|
|
225
235
|
await this.ensureTool(spec.cli);
|
|
226
236
|
this.log.write("info", `setup: disable: disabling ASE ${spec.label} plugin`);
|
|
237
|
+
/* the GitHub Copilot CLI and OpenAI Codex CLI have no "plugin
|
|
238
|
+
disable" subcommand, so uninstall the plugin instead */
|
|
227
239
|
const args = tool === "claude" ?
|
|
228
240
|
["plugin", "disable", "ase@ase"] :
|
|
229
|
-
["plugin",
|
|
241
|
+
["plugin", spec.pRemove, "ase@ase"];
|
|
230
242
|
await this.run(spec.cli, args, { retries: tool === "claude" ? 1 : 3 });
|
|
231
243
|
return 0;
|
|
232
244
|
}
|
|
@@ -242,7 +254,7 @@ export default class SetupCommand {
|
|
|
242
254
|
/* uninstall ASE plugin */
|
|
243
255
|
this.log.write("info", `setup: uninstall${dev ? "[dev]" : ""}: ` +
|
|
244
256
|
`uninstalling ASE ${spec.label} plugin (origin: ${dev ? "local" : "remote/bundled"})`);
|
|
245
|
-
await this.run(spec.cli, ["plugin",
|
|
257
|
+
await this.run(spec.cli, ["plugin", spec.pRemove, "ase@ase"], { ignoreError: `ASE ${spec.label} plugin not installed` });
|
|
246
258
|
await this.run(spec.cli, ["plugin", "marketplace", "remove", "ase"], { ignoreError: `ASE ${spec.label} plugin marketplace not registered` });
|
|
247
259
|
/* uninstall ASE CLI tool (non-development only) */
|
|
248
260
|
if (!dev) {
|
|
@@ -349,7 +361,7 @@ export default class SetupCommand {
|
|
|
349
361
|
/* register an MCP server with the tool, supporting both the "stdio"
|
|
350
362
|
(a local subprocess command) and "http" (a remote URL, optionally
|
|
351
363
|
with HTTP headers) transports; the per-tool command line differs
|
|
352
|
-
between Claude Code
|
|
364
|
+
between Claude Code, GitHub Copilot CLI, and OpenAI Codex CLI */
|
|
353
365
|
async mcpAdd(tool, name, env, transport) {
|
|
354
366
|
const args = ["mcp", "add"];
|
|
355
367
|
if (tool === "claude") {
|
|
@@ -366,7 +378,7 @@ export default class SetupCommand {
|
|
|
366
378
|
args.push(name, transport.url);
|
|
367
379
|
}
|
|
368
380
|
}
|
|
369
|
-
else {
|
|
381
|
+
else if (tool === "copilot") {
|
|
370
382
|
/* GitHub Copilot CLI implies the stdio transport when the
|
|
371
383
|
command is provided after "--"; only "http"/"sse" servers
|
|
372
384
|
need an explicit "--transport" flag and take the URL as a
|
|
@@ -384,10 +396,27 @@ export default class SetupCommand {
|
|
|
384
396
|
args.push(name, transport.url);
|
|
385
397
|
}
|
|
386
398
|
}
|
|
399
|
+
else {
|
|
400
|
+
/* OpenAI Codex CLI takes the server name as the first
|
|
401
|
+
positional argument and implies the stdio transport when the
|
|
402
|
+
command is provided after "--"; "http" servers take the URL
|
|
403
|
+
via the "--url" option (with optional "--header" flags) */
|
|
404
|
+
if (transport.type === "stdio") {
|
|
405
|
+
args.push(name);
|
|
406
|
+
for (const [key, val] of Object.entries(env))
|
|
407
|
+
args.push("--env", `${key}=${val}`);
|
|
408
|
+
args.push("--", ...transport.command);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
for (const [key, val] of Object.entries(transport.headers ?? {}))
|
|
412
|
+
args.push("--header", `${key}: ${val}`);
|
|
413
|
+
args.push(name, "--url", transport.url);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
387
416
|
await this.run(toolSpecs[tool].cli, args);
|
|
388
417
|
}
|
|
389
418
|
/* unregister an MCP server from the tool; the per-tool command line
|
|
390
|
-
differs between Claude Code
|
|
419
|
+
differs between Claude Code, GitHub Copilot CLI, and OpenAI Codex CLI */
|
|
391
420
|
async mcpRemove(tool, name) {
|
|
392
421
|
const args = tool === "claude" ?
|
|
393
422
|
["mcp", "remove", "--scope", "user", name] :
|
|
@@ -527,8 +556,8 @@ export default class SetupCommand {
|
|
|
527
556
|
];
|
|
528
557
|
/* parse and validate the --tool option */
|
|
529
558
|
parseTool(value) {
|
|
530
|
-
if (value !== "claude" && value !== "copilot")
|
|
531
|
-
throw new Error(`invalid --tool value: "${value}" (expected "claude" or "
|
|
559
|
+
if (value !== "claude" && value !== "copilot" && value !== "codex")
|
|
560
|
+
throw new Error(`invalid --tool value: "${value}" (expected "claude", "copilot", or "codex")`);
|
|
532
561
|
return value;
|
|
533
562
|
}
|
|
534
563
|
/* register commands */
|
|
@@ -551,7 +580,7 @@ export default class SetupCommand {
|
|
|
551
580
|
setupCmd
|
|
552
581
|
.command("install")
|
|
553
582
|
.description("install the ASE plugin for a tool")
|
|
554
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
583
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
555
584
|
.option("-d, --dev", "use local working copy instead of remote/bundled repository", devDflt)
|
|
556
585
|
.action(async (opts) => {
|
|
557
586
|
process.exit(await this.doInstall(this.parseTool(opts.tool), opts.dev));
|
|
@@ -560,7 +589,7 @@ export default class SetupCommand {
|
|
|
560
589
|
setupCmd
|
|
561
590
|
.command("update")
|
|
562
591
|
.description("update the ASE tool and the ASE plugin for a tool")
|
|
563
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
592
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
564
593
|
.option("-f, --force", "always perform the update, even if already at latest version", false)
|
|
565
594
|
.option("-d, --dev", "use local working copy instead of remote/bundled repository", devDflt)
|
|
566
595
|
.action(async (opts) => {
|
|
@@ -570,7 +599,7 @@ export default class SetupCommand {
|
|
|
570
599
|
setupCmd
|
|
571
600
|
.command("uninstall")
|
|
572
601
|
.description("uninstall the ASE plugin for a tool and the ASE tool")
|
|
573
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
602
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
574
603
|
.option("-d, --dev", "use local working copy instead of remote/bundled repository", devDflt)
|
|
575
604
|
.action(async (opts) => {
|
|
576
605
|
process.exit(await this.doUninstall(this.parseTool(opts.tool), opts.dev));
|
|
@@ -579,7 +608,7 @@ export default class SetupCommand {
|
|
|
579
608
|
setupCmd
|
|
580
609
|
.command("enable")
|
|
581
610
|
.description("enable the ASE plugin for a tool")
|
|
582
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
611
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
583
612
|
.action(async (opts) => {
|
|
584
613
|
process.exit(await this.doEnable(this.parseTool(opts.tool)));
|
|
585
614
|
});
|
|
@@ -587,7 +616,7 @@ export default class SetupCommand {
|
|
|
587
616
|
setupCmd
|
|
588
617
|
.command("disable")
|
|
589
618
|
.description("disable the ASE plugin for a tool")
|
|
590
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
619
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
591
620
|
.action(async (opts) => {
|
|
592
621
|
process.exit(await this.doDisable(this.parseTool(opts.tool)));
|
|
593
622
|
});
|
|
@@ -610,7 +639,7 @@ export default class SetupCommand {
|
|
|
610
639
|
mcpCmd
|
|
611
640
|
.command("activate [servers]")
|
|
612
641
|
.description("activate pre-defined MCP servers (comma-separated list, or \"all\")")
|
|
613
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
642
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
614
643
|
.action(async (servers, opts) => {
|
|
615
644
|
process.exit(await this.doMcp("activate", this.parseTool(opts.tool), servers ?? "all"));
|
|
616
645
|
});
|
|
@@ -618,7 +647,7 @@ export default class SetupCommand {
|
|
|
618
647
|
mcpCmd
|
|
619
648
|
.command("deactivate [servers]")
|
|
620
649
|
.description("deactivate pre-defined MCP servers (comma-separated list, or \"all\")")
|
|
621
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"
|
|
650
|
+
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
622
651
|
.action(async (servers, opts) => {
|
|
623
652
|
process.exit(await this.doMcp("deactivate", this.parseTool(opts.tool), servers ?? "all"));
|
|
624
653
|
});
|
package/dst/ase-statusline.js
CHANGED
|
@@ -184,8 +184,8 @@ export default class StatuslineCommand {
|
|
|
184
184
|
}
|
|
185
185
|
/* parse and validate the --tool option */
|
|
186
186
|
parseTool(value) {
|
|
187
|
-
if (value !== "claude" && value !== "copilot")
|
|
188
|
-
throw new InvalidArgumentError(`invalid --tool value: "${value}" (expected "claude" or "
|
|
187
|
+
if (value !== "claude" && value !== "copilot" && value !== "codex")
|
|
188
|
+
throw new InvalidArgumentError(`invalid --tool value: "${value}" (expected "claude", "copilot", or "codex")`);
|
|
189
189
|
return value;
|
|
190
190
|
}
|
|
191
191
|
/* register commands */
|
|
@@ -196,7 +196,7 @@ export default class StatuslineCommand {
|
|
|
196
196
|
program
|
|
197
197
|
.command("statusline")
|
|
198
198
|
.description("Render Claude Code or GitHub Copilot CLI statusline from stdin JSON")
|
|
199
|
-
.option("-t, --tool <tool>", "target tool (\"claude\" or \"copilot\")", toolDflt)
|
|
199
|
+
.option("-t, --tool <tool>", "target tool (\"claude\" or \"copilot\"; \"codex\" is unsupported)", toolDflt)
|
|
200
200
|
.option("-w, --width <n>", "force terminal width to <n> characters (0 = auto-detect via /dev/tty)", parseInteger("--width"), 0)
|
|
201
201
|
.option("-m, --margin <n>", "reduce maximum used terminal width by <n> characters on each side", parseInteger("--margin"), 2)
|
|
202
202
|
.option("--no-icons", "disable icons in placeholder rendering")
|
|
@@ -208,6 +208,10 @@ export default class StatuslineCommand {
|
|
|
208
208
|
.action(async (lines, opts) => {
|
|
209
209
|
/* validate target tool */
|
|
210
210
|
const tool = this.parseTool(opts.tool);
|
|
211
|
+
/* OpenAI Codex CLI has no scriptable custom statusline
|
|
212
|
+
mechanism, so reject the request explicitly */
|
|
213
|
+
if (tool === "codex")
|
|
214
|
+
throw new Error("OpenAI Codex CLI does not support a custom statusline");
|
|
211
215
|
/* read all of stdin */
|
|
212
216
|
const input = await readStdin();
|
|
213
217
|
/* parse JSON data */
|
package/dst/ase.js
CHANGED
|
@@ -15,6 +15,7 @@ import SetupCommand from "./ase-setup.js";
|
|
|
15
15
|
import StatuslineCommand from "./ase-statusline.js";
|
|
16
16
|
import TaskCommand from "./ase-task.js";
|
|
17
17
|
import ArtifactCommand from "./ase-artifact.js";
|
|
18
|
+
import CompatCommand from "./ase-compat.js";
|
|
18
19
|
import pkg from "../package.json" with { type: "json" };
|
|
19
20
|
/* globally initialize logger */
|
|
20
21
|
const log = new Log("ase", "warning", "-");
|
|
@@ -51,6 +52,7 @@ const main = async () => {
|
|
|
51
52
|
new StatuslineCommand(log).register(program);
|
|
52
53
|
new TaskCommand(log).register(program);
|
|
53
54
|
new ArtifactCommand(log).register(program);
|
|
55
|
+
new CompatCommand().register(program);
|
|
54
56
|
new DiagramCommand(log).register(program);
|
|
55
57
|
/* parse program arguments */
|
|
56
58
|
await program.parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "http://github.com/rse/ase",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "http://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.12",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"which": "7.0.0",
|
|
59
59
|
"update-notifier": "7.3.1",
|
|
60
60
|
"shell-quote": "1.8.4",
|
|
61
|
+
"get-stdin": "10.0.0",
|
|
61
62
|
"proper-lockfile": "4.1.2",
|
|
62
63
|
"write-file-atomic": "8.0.0",
|
|
63
64
|
"pacote": "21.5.1",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ase",
|
|
3
|
+
"version": "0.9.12",
|
|
4
|
+
"description": "Agentic Software Engineering (ASE)",
|
|
5
|
+
"keywords": [ "agentic", "software", "engineering" ],
|
|
6
|
+
"homepage": "https://ase.tools",
|
|
7
|
+
"repository": "https://github.com/rse/ase",
|
|
8
|
+
"license": "GPL-3.0",
|
|
9
|
+
"author": {
|
|
10
|
+
"name": "Dr. Ralf S. Engelschall",
|
|
11
|
+
"email": "rse@engelschall.com",
|
|
12
|
+
"url": "https://engelschall.com"
|
|
13
|
+
},
|
|
14
|
+
"skills": "./skills/",
|
|
15
|
+
"hooks": "./hooks/hooks-codex.json",
|
|
16
|
+
"mcpServers": "./.codex-plugin/mcp.json"
|
|
17
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "ase hook session-start --tool codex",
|
|
9
|
+
"statusMessage": "Loading ASE..."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"SessionEnd": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "ase hook session-end --tool codex",
|
|
20
|
+
"statusMessage": "Cleaning up ASE..."
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "ase hook pre-tool-use --tool codex"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"PermissionRequest": [
|
|
36
|
+
{
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "ase hook permission-request --tool codex",
|
|
41
|
+
"statusMessage": "Approve ASE..."
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"UserPromptSubmit": [
|
|
47
|
+
{
|
|
48
|
+
"hooks": [
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "ase hook user-prompt-submit --tool codex"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"Stop": [
|
|
57
|
+
{
|
|
58
|
+
"hooks": [
|
|
59
|
+
{
|
|
60
|
+
"type": "command",
|
|
61
|
+
"command": "ase hook stop --tool codex"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -9,6 +9,9 @@ User Dialog
|
|
|
9
9
|
<if condition="<ase-agent-tool/> is 'copilot'">
|
|
10
10
|
<user-dialog-tool>ask_user</user-dialog-tool>
|
|
11
11
|
</if>
|
|
12
|
+
<if condition="<ase-agent-tool/> is 'codex'">
|
|
13
|
+
<user-dialog-tool>none</user-dialog-tool>
|
|
14
|
+
</if>
|
|
12
15
|
|
|
13
16
|
<define name="user-dialog">
|
|
14
17
|
|
|
@@ -138,6 +141,23 @@ Let the *user interactively choose* an answer.
|
|
|
138
141
|
|
|
139
142
|
</if>
|
|
140
143
|
|
|
144
|
+
- <if condition="<ase-agent-tool/> is 'codex'">
|
|
145
|
+
|
|
146
|
+
OpenAI Codex CLI has *no* interactive user-dialog tool, so you
|
|
147
|
+
*MUST* *NOT* call any tool here. Instead, render the question and
|
|
148
|
+
answers as a custom Markdown dialog and let the user reply with a
|
|
149
|
+
free-text choice -- exactly as defined by the following expansion,
|
|
150
|
+
passing <spec/> as its question specification, and set <result/> to
|
|
151
|
+
the result of that custom dialog:
|
|
152
|
+
|
|
153
|
+
<expand name="custom-dialog" arg1="--other">
|
|
154
|
+
<spec/>
|
|
155
|
+
</expand>
|
|
156
|
+
|
|
157
|
+
Do not output anything in this step!
|
|
158
|
+
|
|
159
|
+
</if>
|
|
160
|
+
|
|
141
161
|
</define>
|
|
142
162
|
|
|
143
163
|
<define name="custom-dialog">
|
package/plugin/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "http://github.com/rse/ase",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "http://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.12",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -249,7 +249,7 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
249
249
|
a question with the following custom dialog, where per
|
|
250
250
|
approach A<n/>, you determine an ultra brief summary
|
|
251
251
|
<short-summary/> and then use the answer option `A<n/>:
|
|
252
|
-
⚝ **RECOMMENDATION**
|
|
252
|
+
⚝ **RECOMMENDATION** ⚝ - <short-summary/>` for your
|
|
253
253
|
recommended approach plus zero or more answer options `A<n/>:
|
|
254
254
|
<short-summary/>` for all other approaches:
|
|
255
255
|
|
|
@@ -238,7 +238,7 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
238
238
|
a question with the following custom dialog, where per
|
|
239
239
|
approach A<n/>, you determine an ultra brief summary
|
|
240
240
|
<short-summary/> and then use the answer option `A<n/>:
|
|
241
|
-
⚝ **RECOMMENDATION**
|
|
241
|
+
⚝ **RECOMMENDATION** ⚝ - <short-summary/>` for your
|
|
242
242
|
recommended approach plus zero or more answer options `A<n/>:
|
|
243
243
|
<short-summary/>` for all other approaches:
|
|
244
244
|
|
|
@@ -289,7 +289,7 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
|
|
|
289
289
|
a question with the following custom dialog, where per
|
|
290
290
|
approach A<n/>, you determine an ultra brief summary
|
|
291
291
|
<short-summary/> and then use the answer option `A<n/>:
|
|
292
|
-
⚝ **RECOMMENDATION**
|
|
292
|
+
⚝ **RECOMMENDATION** ⚝ - <short-summary/>` for your
|
|
293
293
|
recommended approach plus zero or more answer options `A<n/>:
|
|
294
294
|
<short-summary/>` for all other approaches:
|
|
295
295
|
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ase-meta-compat
|
|
3
|
+
description: >
|
|
4
|
+
Self-test the LLM's ability to execute ASE's core interpreter
|
|
5
|
+
machinery (control flow, XML placeholders, regex matching,
|
|
6
|
+
arithmetic) and report an overall compatibility rating. Use when
|
|
7
|
+
the user wants to check how well the current model/harness is
|
|
8
|
+
compatible with ASE, or asks to "test ASE compatibility" or "check
|
|
9
|
+
ASE compatibility".
|
|
10
|
+
user-invocable: true
|
|
11
|
+
disable-model-invocation: true
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
@${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
|
|
15
|
+
|
|
16
|
+
Self-Test ASE Compatibility
|
|
17
|
+
===========================
|
|
18
|
+
|
|
19
|
+
Objective
|
|
20
|
+
---------
|
|
21
|
+
|
|
22
|
+
*Self-test* how faithfully the current LLM (and its harness) executes the
|
|
23
|
+
four *core interpreter primitives* that every ASE skill silently relies
|
|
24
|
+
on -- *control flow*, *XML placeholders*, *regex matching*, and
|
|
25
|
+
*arithmetic* -- and report an overall **0%…100% compatibility** rating.
|
|
26
|
+
|
|
27
|
+
This skill is unusual: it is both the *test definition* and the *system
|
|
28
|
+
under test*. Each *probe* is a pure ASE construct that you execute and
|
|
29
|
+
record its *actual* result. You *MUST NOT* compare against any expected
|
|
30
|
+
value until STEP 6. Do not fabricate passes: a probe whose actual result
|
|
31
|
+
differs from its expected result *MUST* be recorded as a *fail*.
|
|
32
|
+
|
|
33
|
+
Notice that this skill intentionally does not strictly follow the ASE
|
|
34
|
+
skill format, especially it does not include any meta skill information,
|
|
35
|
+
etc.
|
|
36
|
+
|
|
37
|
+
Procedure
|
|
38
|
+
---------
|
|
39
|
+
|
|
40
|
+
1. STEP 1: Initialize Self-Test
|
|
41
|
+
|
|
42
|
+
Initialize the running map of actual probe results <actuals></actuals>
|
|
43
|
+
(set to empty) -- every probe in STEPs 2-5 stores its result here as
|
|
44
|
+
a `<category/>/<probe-name/>` entry with its *actual* computed value.
|
|
45
|
+
|
|
46
|
+
Initialize the running list of failed probes <failures></failures>
|
|
47
|
+
(set to empty) -- this is populated only in STEP 6 after comparing
|
|
48
|
+
actuals against the expected values.
|
|
49
|
+
|
|
50
|
+
2. STEP 2: Probe XML Placeholders
|
|
51
|
+
|
|
52
|
+
Execute *all* of the following XML-placeholder probes (in the given
|
|
53
|
+
order). For each probe, record the *actual* result in <actuals/>
|
|
54
|
+
under the key `xml-placeholders/<probe-name/>`. Do *not* compare to
|
|
55
|
+
any expected value here.
|
|
56
|
+
|
|
57
|
+
1. *get-and-set*: Set <v>42</v>, then get <v/>.
|
|
58
|
+
Record actual result for `xml-placeholders/get-and-set`.
|
|
59
|
+
|
|
60
|
+
2. *self-ref*: Set <v>42</v>, then set `<v>pre_<v/></v>`, then get <v/>.
|
|
61
|
+
Record actual result for `xml-placeholders/self-ref`.
|
|
62
|
+
|
|
63
|
+
3. *overwrite*: Set <v>42</v>, then set `<v>99</v>`, then get <v/>.
|
|
64
|
+
Record actual result for `xml-placeholders/overwrite`.
|
|
65
|
+
|
|
66
|
+
4. *indexed*: Set `<eval-1-2>+1</eval-1-2>` and `<eval-2-1>-1</eval-2-1>`,
|
|
67
|
+
then get <eval-1-2/> and <eval-2-1/> via `<eval-1-2/>,<eval-2-1/>`.
|
|
68
|
+
Record actual result for `xml-placeholders/indexed`.
|
|
69
|
+
|
|
70
|
+
5. *nested-attr*: With <w>20</w>, build the value of an XML attribute
|
|
71
|
+
`width="<w/>"` and read the attribute `width` back.
|
|
72
|
+
Record actual result for `xml-placeholders/nested-attr`.
|
|
73
|
+
|
|
74
|
+
6. *entity*: Evaluate the XML entity `○` and get the rendered
|
|
75
|
+
Unicode character.
|
|
76
|
+
Record actual result for `xml-placeholders/entity`.
|
|
77
|
+
|
|
78
|
+
Set <xml-total/> to the number of XML-placeholder probes above.
|
|
79
|
+
|
|
80
|
+
3. STEP 3: Probe Control Flow
|
|
81
|
+
|
|
82
|
+
Execute *all* of the following control-flow probes (in the given
|
|
83
|
+
order). For each probe, record the *actual* result in <actuals/>
|
|
84
|
+
under the key `control-flow/<probe-name/>`. Do *not* compare to any
|
|
85
|
+
expected value here.
|
|
86
|
+
|
|
87
|
+
1. *branch*: Set <a>3</a>, then evaluate
|
|
88
|
+
`<if condition="<a/> is greater than 5">big</if>
|
|
89
|
+
<elseif condition="<a/> is greater than 2">mid</elseif>
|
|
90
|
+
<else>small</else>`.
|
|
91
|
+
Record actual result for `control-flow/branch`.
|
|
92
|
+
|
|
93
|
+
2. *while-sum*: Set <s>0</s> and <i>1</i>, then evaluate
|
|
94
|
+
`<while condition="<i/> is less than or equal to 5">
|
|
95
|
+
set <s/> to <s/> + <i/>,
|
|
96
|
+
set <i/> to <i/> + 1
|
|
97
|
+
</while>`.
|
|
98
|
+
Record <s/> as actual result for `control-flow/while-sum`.
|
|
99
|
+
|
|
100
|
+
3. *for-order*: Set <s></s>, then evaluate
|
|
101
|
+
`<for items="x y z"><s><s/>-<item/></for>`.
|
|
102
|
+
Record <s/> as actual result for `control-flow/for-order`.
|
|
103
|
+
|
|
104
|
+
4. *while-break*: Set <i>1</i> and <hit></hit>, then evaluate
|
|
105
|
+
`<while condition="<i/> is less than or equal to 9">
|
|
106
|
+
if <i/> equals 4 set <hit/> to <i/> and <break/>,
|
|
107
|
+
else set <i/> to <i/> + 1
|
|
108
|
+
</while>`.
|
|
109
|
+
Record <hit/> as actual result for `control-flow/while-break`.
|
|
110
|
+
|
|
111
|
+
5. *step-skip*: A `<step condition="[...]">[...]</step>` either expands
|
|
112
|
+
to its body (if condition evaluates to true) or to the empty string,
|
|
113
|
+
so what does `<step condition="42 is greater than 7">X</step>` expands into?
|
|
114
|
+
Record the result of this construct for `control-flow/step-skip`.
|
|
115
|
+
|
|
116
|
+
6. *expand-subst*: With `<define name="foo">[<arg1/>:<content/>]</define>`,
|
|
117
|
+
evaluate `<expand name="foo" arg1="K">V</expand>`.
|
|
118
|
+
Record actual result for `control-flow/expand-subst`.
|
|
119
|
+
|
|
120
|
+
Set <cf-total/> to the number of control-flow probes above.
|
|
121
|
+
|
|
122
|
+
4. STEP 4: Probe Regex Matching
|
|
123
|
+
|
|
124
|
+
Execute *all* of the following regex probes (in the given order).
|
|
125
|
+
For each probe, record the *actual* result in <actuals/> under the
|
|
126
|
+
key `regex/<probe-name/>`. Do *not* compare to any expected value
|
|
127
|
+
here.
|
|
128
|
+
|
|
129
|
+
1. *getopt-dash*: Does the string `-l foo` match the regexp `(^|\s)-`?
|
|
130
|
+
Record `yes` or `no` as actual result for `regex/getopt-dash`.
|
|
131
|
+
|
|
132
|
+
2. *getopt-nodash*: Does the string `foo` match the regexp `(^|\s)-`?
|
|
133
|
+
Record `yes` or `no` as actual result for `regex/getopt-nodash`.
|
|
134
|
+
|
|
135
|
+
3. *anchored-int*: Does `123` fully match `^\d+$`, and does `12a` fully
|
|
136
|
+
match `^\d+$`? Report as `<yes-or-no/>,<yes-or-no/>`.
|
|
137
|
+
Record actual result for `regex/anchored-int`.
|
|
138
|
+
|
|
139
|
+
4. *alternation*: Does `thorough` match `^(basic|standard|thorough)$`?
|
|
140
|
+
Record `yes` or `no` as actual result for `regex/alternation`.
|
|
141
|
+
|
|
142
|
+
5. *capture*: Apply `^--(\w+).+` to `--foo-bar-quux`.
|
|
143
|
+
Record the first capture group as actual result for `regex/capture`.
|
|
144
|
+
|
|
145
|
+
6. *whitespace*: Does ` -x` (leading space then dash) match `(^|\s)-`
|
|
146
|
+
(the `\s` alternative)?
|
|
147
|
+
Record `yes` or `no` as actual result for `regex/whitespace`.
|
|
148
|
+
|
|
149
|
+
7. *complex*: Does `--level=(low|high)...` match
|
|
150
|
+
`^--([A-Za-z][A-Za-z0-9-]*)(?:\|-([A-Za-z]))?(?:=(\((.*)\)(\.\.\.)?|.*))?$`?
|
|
151
|
+
Record `yes` or `no` as actual result for `regex/complex`.
|
|
152
|
+
|
|
153
|
+
Set <re-total/> to the number of regex probes above.
|
|
154
|
+
|
|
155
|
+
</step>
|
|
156
|
+
|
|
157
|
+
5. STEP 5: Probe Arithmetic
|
|
158
|
+
|
|
159
|
+
Execute *all* of the following arithmetic probes (in the given
|
|
160
|
+
order). For each probe, record the *actual* result in <actuals/>
|
|
161
|
+
under the key `arithmetic/<probe-name/>`. Do *not* compare to any
|
|
162
|
+
expected value here.
|
|
163
|
+
|
|
164
|
+
1. *increment*: With <n>7</n>, compute <n/> + 2.
|
|
165
|
+
Record actual result for `arithmetic/increment`.
|
|
166
|
+
|
|
167
|
+
2. *product-sum*: Compute `4.00 * 1.00 + 2.00 * 0.50`.
|
|
168
|
+
Record actual result (as `X.XX`) for `arithmetic/product-sum`.
|
|
169
|
+
|
|
170
|
+
3. *percentage*: Compute `3 / 7` rounded to 2 decimal places.
|
|
171
|
+
Record actual result (as `0.XX`) for `arithmetic/percentage`.
|
|
172
|
+
|
|
173
|
+
4. *bar-width*: With <title-len>13</title-len>, compute `67 - <title-len/>`.
|
|
174
|
+
Record actual result for `arithmetic/bar-width`.
|
|
175
|
+
|
|
176
|
+
5. *threshold*: Is `0.095` less than `0.10`?
|
|
177
|
+
Record `yes` or `no` as actual result for `arithmetic/threshold`.
|
|
178
|
+
|
|
179
|
+
6. *round-half*: Round `2.5` to the nearest integer (round half up).
|
|
180
|
+
Record actual result for `arithmetic/round-half`.
|
|
181
|
+
|
|
182
|
+
Set <arith-total/> to the number of arithmetic probes above.
|
|
183
|
+
|
|
184
|
+
</step>
|
|
185
|
+
|
|
186
|
+
6. STEP 6: Fetch Expected Values and Score
|
|
187
|
+
|
|
188
|
+
Call the `ase_compat()` tool of the `ase` MCP server with an
|
|
189
|
+
*internal* *programmatic* MCP tool call (and *NOT* with an external
|
|
190
|
+
shell command) and set <compat-output/> to the `text` content of the
|
|
191
|
+
response.
|
|
192
|
+
|
|
193
|
+
Parse <compat-output/> into a map <expected/> by splitting on
|
|
194
|
+
newlines; for each non-empty line of the form `<id/>: <value/>`, set
|
|
195
|
+
`<expected[<id/>]>` to `<value/>` (value may be empty for probes
|
|
196
|
+
whose correct answer is an empty string).
|
|
197
|
+
|
|
198
|
+
For each probe in <actuals/>, compare the actual result to
|
|
199
|
+
`<expected[<id/>]>` using an exact-match comparison:
|
|
200
|
+
|
|
201
|
+
- If they match, count the probe as *pass*.
|
|
202
|
+
- If they differ, count the probe as *fail* and append `<id/>`
|
|
203
|
+
to <failures/>.
|
|
204
|
+
|
|
205
|
+
Tally pass counts per category:
|
|
206
|
+
|
|
207
|
+
- Set <xml-pass/> to the number of `xml-placeholders/*` probes that passed.
|
|
208
|
+
- Set <cf-pass/> to the number of `control-flow/*` probes that passed.
|
|
209
|
+
- Set <re-pass/> to the number of `regex/*` probes that passed.
|
|
210
|
+
- Set <arith-pass/> to the number of `arithmetic/*` probes that passed.
|
|
211
|
+
|
|
212
|
+
</step>
|
|
213
|
+
|
|
214
|
+
7. STEP 7: Compute and Report Compatibility
|
|
215
|
+
|
|
216
|
+
Compute each category's *pass-rate* (a value in 0.0…1.0) by dividing
|
|
217
|
+
its passes by its total:
|
|
218
|
+
|
|
219
|
+
set <xml-rate/> to <xml-pass/> / <xml-total/>,
|
|
220
|
+
set <cf-rate/> to <cf-pass/> / <cf-total/>,
|
|
221
|
+
set <re-rate/> to <re-pass/> / <re-total/>,
|
|
222
|
+
set <arith-rate/> to <arith-pass/> / <arith-total/>.
|
|
223
|
+
|
|
224
|
+
Compute the overall compatibility as a *weighted average* of the
|
|
225
|
+
four pass-rates, where *XML placeholders* carry weight `4.00` (the
|
|
226
|
+
backbone of every skill), *control flow* carries weight `3.00`,
|
|
227
|
+
*regex matching* carries weight `2.00`, and *arithmetic* carries
|
|
228
|
+
weight `1.00`. Use the `ase_decision_matrix` MCP tool with a single
|
|
229
|
+
"compatibility" alternative column, one matrix row per category of
|
|
230
|
+
the form `[ <weight/>, <rate/> ]`:
|
|
231
|
+
|
|
232
|
+
Call...
|
|
233
|
+
|
|
234
|
+
`ase_decision_matrix(matrix: [
|
|
235
|
+
[ 4.00, <xml-rate/> ],
|
|
236
|
+
[ 3.00, <cf-rate/> ],
|
|
237
|
+
[ 2.00, <re-rate/> ],
|
|
238
|
+
[ 1.00, <arith-rate/> ]
|
|
239
|
+
])`
|
|
240
|
+
|
|
241
|
+
...of the `ase` MCP server with an *internal* *programmatic* MCP
|
|
242
|
+
tool call (and *NOT* with an external shell command) and set
|
|
243
|
+
<product-sum/> to the *single* numerical value of the returned
|
|
244
|
+
array.
|
|
245
|
+
|
|
246
|
+
The product-sum is the sum over the four rows of `<weight/> *
|
|
247
|
+
<rate/>`, so dividing by the sum of weights (4.00 + 3.00 + 2.00 +
|
|
248
|
+
1.00 = 10.00) yields the weighted-average compatibility on 0.0…1.0:
|
|
249
|
+
|
|
250
|
+
Set <overall/> to <product-sum/> / 10.00.
|
|
251
|
+
Set <overall-pct/> to round(<overall/> * 100) (an integer percentage).
|
|
252
|
+
|
|
253
|
+
Also compute each category's display percentage:
|
|
254
|
+
|
|
255
|
+
Set <cf-pct/> to round(<cf-rate/> * 100), and
|
|
256
|
+
likewise <xml-pct/>, <re-pct/>, <arith-pct/>.
|
|
257
|
+
|
|
258
|
+
Determine the <verdict/> from <overall-pct/>:
|
|
259
|
+
|
|
260
|
+
1. If <overall-pct/> is greater than or equal to 90:
|
|
261
|
+
Set <verdict>✓ *FULLY COMPATIBLE*</verdict>.
|
|
262
|
+
|
|
263
|
+
2. Else if <overall-pct/> is greater than or equal to 60:
|
|
264
|
+
Set <verdict>⚠ *PARTIALLY COMPATIBLE*</verdict>.
|
|
265
|
+
|
|
266
|
+
3. Else:
|
|
267
|
+
Set <verdict>✘ *INCOMPATIBLE*</verdict>.
|
|
268
|
+
|
|
269
|
+
4. If <failures/> is not empty:
|
|
270
|
+
Append to <verdict/> the suffix ` (failed: <failures/>)`.
|
|
271
|
+
|
|
272
|
+
Output the final compatibility report with the following <template/>:
|
|
273
|
+
|
|
274
|
+
<template>
|
|
275
|
+
|
|
276
|
+
**RESULTS**:
|
|
277
|
+
|
|
278
|
+
| ⦿ *Capability* | ⚖ *Weight* | ✓ *Passed* | ⚑ *Rate* |
|
|
279
|
+
| :------------------- | ----------: | ---------------------------: | ------------------: |
|
|
280
|
+
| XML Placeholders | 4.00 | <xml-pass/>/<xml-total/> | <xml-pct/>% |
|
|
281
|
+
| Control Flow | 3.00 | <cf-pass/>/<cf-total/> | <cf-pct/>% |
|
|
282
|
+
| Regex Matching | 2.00 | <re-pass/>/<re-total/> | <re-pct/>% |
|
|
283
|
+
| Arithmetic | 1.00 | <arith-pass/>/<arith-total/> | <arith-pct/>% |
|
|
284
|
+
| **OVERALL** | | | **<overall-pct/>%** |
|
|
285
|
+
|
|
286
|
+
**VERDICT**: <verdict/>
|
|
287
|
+
|
|
288
|
+
</template>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
## NAME
|
|
3
|
+
|
|
4
|
+
`ase-meta-compat` - Self-Test ASE Compatibility
|
|
5
|
+
|
|
6
|
+
## SYNOPSIS
|
|
7
|
+
|
|
8
|
+
`ase-meta-compat`
|
|
9
|
+
[`--help`|`-h`]
|
|
10
|
+
|
|
11
|
+
## DESCRIPTION
|
|
12
|
+
|
|
13
|
+
The `ase-meta-compat` skill *self-tests* how faithfully the current LLM
|
|
14
|
+
(and its harness) executes the four *core interpreter primitives* that
|
|
15
|
+
every ASE skill silently relies on, and reports an overall *0%…100%
|
|
16
|
+
compatibility* rating. ASE skills are not run by a conventional
|
|
17
|
+
interpreter - they are interpreted by the *model itself*, which must honor
|
|
18
|
+
control-flow constructs, substitute XML placeholders, evaluate regex
|
|
19
|
+
conditions, and perform arithmetic. If a model cannot execute these
|
|
20
|
+
primitives reliably, *every* ASE skill silently misbehaves, so this skill
|
|
21
|
+
lets a user measure the fit *before* relying on ASE.
|
|
22
|
+
|
|
23
|
+
The skill is both the *test definition* and the *system under test*. It
|
|
24
|
+
probes four capability categories:
|
|
25
|
+
|
|
26
|
+
- *XML Placeholders*: the get (`<x/>`) and set (`<x>v</x>`) semantics,
|
|
27
|
+
including the get-and-set composite, indexed names, and XML-entity
|
|
28
|
+
rendering.
|
|
29
|
+
|
|
30
|
+
- *Control Flow*: the `<if>`/`<elseif>`/`<else>`, `<while>`/`<break>`,
|
|
31
|
+
`<for>`, `<step condition=…>`, and `<expand>`/`<define>` constructs.
|
|
32
|
+
|
|
33
|
+
- *Regex Matching*: the patterns ASE genuinely uses (e.g. the getopt
|
|
34
|
+
short-circuit `(^|\s)-`), plus anchoring, alternation, and captures.
|
|
35
|
+
|
|
36
|
+
- *Arithmetic*: counter increments, decision-matrix product-sums,
|
|
37
|
+
percentage rounding, and bar-width computations.
|
|
38
|
+
|
|
39
|
+
Each *probe* embeds a construct with a *known expected result*; the model
|
|
40
|
+
executes it and honestly self-checks its actual result against the
|
|
41
|
+
expected one. Per-category pass-rates are combined into the overall rating
|
|
42
|
+
via the `ase_decision_matrix` MCP tool as a *weighted average*, with
|
|
43
|
+
*XML Placeholders* weighted `4.00` (the backbone of every skill),
|
|
44
|
+
*Control Flow* weighted `3.00`, *Regex Matching* weighted `2.00`, and
|
|
45
|
+
*Arithmetic* weighted `1.00`. The
|
|
46
|
+
result is a boxed per-category table (passes and percentage per category),
|
|
47
|
+
a final *OVERALL COMPATIBILITY* percentage, and a one-line *VERDICT*
|
|
48
|
+
(`✓ fully compatible` at `≥ 90%`, `⚠ partially compatible` at `60–89%`,
|
|
49
|
+
`✘ incompatible` below `60%`) that names any failed probes.
|
|
50
|
+
|
|
51
|
+
## ARGUMENTS
|
|
52
|
+
|
|
53
|
+
The `ase-meta-compat` skill takes no arguments (besides `--help`|`-h`). It
|
|
54
|
+
always runs the most rigorous self-test, with 6 *probes per category*
|
|
55
|
+
(7 for *Regex Matching*), for 25 probes in total.
|
|
56
|
+
|
|
57
|
+
## EXAMPLES
|
|
58
|
+
|
|
59
|
+
Run the compatibility self-test:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
❯ /ase-meta-compat
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## SEE ALSO
|
|
66
|
+
|
|
67
|
+
[`ase-meta-evaluate`](../ase-meta-evaluate/help.md), [`ase-meta-quorum`](../ase-meta-quorum/help.md), [`ase-meta-why`](../ase-meta-why/help.md).
|
|
@@ -195,7 +195,7 @@ explicitly requested by this procedure via outputs based on a <template/>!
|
|
|
195
195
|
|
|
196
196
|
<expand name="custom-dialog" arg1="--other">
|
|
197
197
|
<aspect-N/>: <question-N/>
|
|
198
|
-
<answer-N-1-label/>: ⚝ **CURRENT PLAN** ⚝ <answer-N-1-description/>
|
|
198
|
+
<answer-N-1-label/>: ⚝ **CURRENT PLAN** ⚝ - <answer-N-1-description/>
|
|
199
199
|
<answer-N-K-label/>: <answer-N-K-description/>
|
|
200
200
|
[...]
|
|
201
201
|
</expand>
|