@rse/ase 0.0.16 → 0.0.17
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.js +1 -1
- package/dst/ase-hook.js +87 -10
- package/dst/ase-setup.js +31 -20
- package/dst/ase-version.js +27 -0
- package/package.json +5 -3
package/dst/ase-config.js
CHANGED
|
@@ -151,7 +151,7 @@ const hasProjectContext = () => {
|
|
|
151
151
|
"project" term is implicitly added only when a project context
|
|
152
152
|
exists (Git repository or ".ase" directory at or above cwd), and
|
|
153
153
|
an explicit "project" term requires that same context */
|
|
154
|
-
const parseScope = (value) => {
|
|
154
|
+
export const parseScope = (value) => {
|
|
155
155
|
const projectActive = hasProjectContext();
|
|
156
156
|
const input = (value === undefined || value === "") ?
|
|
157
157
|
(projectActive ? "project" : "user") :
|
package/dst/ase-hook.js
CHANGED
|
@@ -6,14 +6,40 @@
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import { execaSync } from "execa";
|
|
9
|
+
import Version from "./ase-version.js";
|
|
10
|
+
import { Config, configSchema, parseScope } from "./ase-config.js";
|
|
9
11
|
/* CLI command "ase hook" */
|
|
10
12
|
export default class HookCommand {
|
|
11
13
|
log;
|
|
12
14
|
constructor(log) {
|
|
13
15
|
this.log = log;
|
|
14
16
|
}
|
|
17
|
+
/* recursively expand "@<path>" file references in a Markdown text,
|
|
18
|
+
resolving paths relative to the directory of the containing file */
|
|
19
|
+
expandReferences(text, baseDir, visited = new Set()) {
|
|
20
|
+
return text.replace(/@([^\s]+)/g, (match, ref) => {
|
|
21
|
+
let resolved = ref;
|
|
22
|
+
if (resolved.startsWith("~/"))
|
|
23
|
+
resolved = path.join(process.env.HOME ?? "", resolved.slice(2));
|
|
24
|
+
const abs = path.isAbsolute(resolved) ? resolved : path.resolve(baseDir, resolved);
|
|
25
|
+
if (visited.has(abs))
|
|
26
|
+
return match;
|
|
27
|
+
if (!fs.existsSync(abs))
|
|
28
|
+
return match;
|
|
29
|
+
let content;
|
|
30
|
+
try {
|
|
31
|
+
content = fs.readFileSync(abs, "utf8");
|
|
32
|
+
}
|
|
33
|
+
catch (_e) {
|
|
34
|
+
return match;
|
|
35
|
+
}
|
|
36
|
+
const next = new Set(visited);
|
|
37
|
+
next.add(abs);
|
|
38
|
+
return this.expandReferences(content, path.dirname(abs), next);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
15
41
|
/* handler for "ase hook session-start" */
|
|
16
|
-
doSessionStart() {
|
|
42
|
+
async doSessionStart() {
|
|
17
43
|
/* determine plugin root */
|
|
18
44
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ?? "";
|
|
19
45
|
if (pluginRoot === "")
|
|
@@ -25,34 +51,85 @@ export default class HookCommand {
|
|
|
25
51
|
const pkg = fs.readFileSync(filePkg, "utf8");
|
|
26
52
|
let md = fs.readFileSync(fileMd, "utf8");
|
|
27
53
|
/* determine own version */
|
|
28
|
-
const
|
|
54
|
+
const versionCurrentPlugin = JSON.parse(pkg).version ?? "";
|
|
55
|
+
const versionCurrentTool = Version.current();
|
|
56
|
+
const versionLatestTool = await Version.latest();
|
|
57
|
+
/* sanity check situation */
|
|
58
|
+
const versionHints = [];
|
|
59
|
+
if (versionCurrentPlugin !== versionCurrentTool)
|
|
60
|
+
versionHints.push("**WARNING:** version *mismatch*: " +
|
|
61
|
+
`tool: **${versionCurrentPlugin}**, plugin: **${versionCurrentTool}**`);
|
|
62
|
+
if (versionCurrentTool !== versionLatestTool)
|
|
63
|
+
versionHints.push(`**NOTICE:** *latest* version: **${versionLatestTool}**, please update!`);
|
|
64
|
+
if (process.env.ASE_SETUP_DEV !== undefined)
|
|
65
|
+
versionHints.push("**NOTICE:** *development* setup");
|
|
66
|
+
const versionHint = versionHints.length > 0 ? "(" + versionHints.join(", ") + ")" : "";
|
|
29
67
|
/* read session information */
|
|
30
68
|
const stdin = fs.readFileSync(0, "utf8");
|
|
31
69
|
const input = stdin.trim() !== "" ? JSON.parse(stdin) : {};
|
|
32
70
|
/* determine session id */
|
|
33
71
|
const sessionId = input.session_id ?? "";
|
|
72
|
+
/* establish config context */
|
|
73
|
+
const cfg = new Config("config", configSchema, this.log, parseScope(`session:${sessionId}`));
|
|
74
|
+
try {
|
|
75
|
+
cfg.read();
|
|
76
|
+
}
|
|
77
|
+
catch (_e) {
|
|
78
|
+
/* best-effort: ignore failures */
|
|
79
|
+
}
|
|
34
80
|
/* determine task id */
|
|
35
81
|
const taskId = process.env.ASE_TASK_ID ?? "default";
|
|
36
82
|
try {
|
|
37
|
-
|
|
83
|
+
cfg.set("task.id", taskId);
|
|
84
|
+
cfg.write();
|
|
38
85
|
}
|
|
39
86
|
catch (_e) {
|
|
40
87
|
/* best-effort: ignore failures */
|
|
41
88
|
}
|
|
42
|
-
/*
|
|
89
|
+
/* determine project id */
|
|
90
|
+
const cwd = input.cwd ?? process.cwd();
|
|
91
|
+
let projectDir = cwd;
|
|
92
|
+
try {
|
|
93
|
+
const result = execaSync("git", ["rev-parse", "--show-toplevel"], {
|
|
94
|
+
stderr: "ignore", cwd
|
|
95
|
+
});
|
|
96
|
+
if (result.stdout.trim() !== "")
|
|
97
|
+
projectDir = result.stdout.trim();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* not inside a Git working tree */
|
|
101
|
+
}
|
|
102
|
+
const projectId = path.basename(projectDir);
|
|
103
|
+
/* determine user id */
|
|
104
|
+
const userId = process.env.USER ?? process.env.LOGNAME ?? "unknown";
|
|
105
|
+
/* determine agent persona style */
|
|
106
|
+
let persona = "engineer";
|
|
107
|
+
const val = cfg.get("agent.persona.style");
|
|
108
|
+
if (typeof val === "string")
|
|
109
|
+
persona = val;
|
|
110
|
+
/* provide ASE information to Claude Code shell commands */
|
|
43
111
|
const envFile = process.env.CLAUDE_ENV_FILE ?? "";
|
|
44
112
|
if (envFile !== "") {
|
|
45
|
-
const script = `export ASE_VERSION="${
|
|
46
|
-
`export
|
|
47
|
-
`export
|
|
113
|
+
const script = `export ASE_VERSION="${versionCurrentPlugin}"\n` +
|
|
114
|
+
`export ASE_USER_ID="${userId}"\n` +
|
|
115
|
+
`export ASE_PROJECT_ID="${projectId}"\n` +
|
|
116
|
+
`export ASE_TASK_ID="${taskId}"\n` +
|
|
117
|
+
`export ASE_SESSION_ID="${sessionId}"\n`;
|
|
48
118
|
fs.appendFileSync(envFile, script, "utf8");
|
|
49
119
|
}
|
|
50
120
|
/* prepend ASE information to constitution markdown */
|
|
51
121
|
md =
|
|
52
|
-
`<ase-version>${
|
|
122
|
+
`<ase-version>${versionCurrentPlugin}</ase-version>\n` +
|
|
123
|
+
`<ase-version-hint>${versionHint}</ase-version-hint>\n` +
|
|
124
|
+
`<ase-persona-style>${persona}</ase-persona-style>\n` +
|
|
125
|
+
`<ase-user-id>${userId}</ase-user-id>\n` +
|
|
126
|
+
`<ase-project-id>${projectId}</ase-project-id>\n` +
|
|
53
127
|
`<ase-task-id>${taskId}</ase-task-id>\n` +
|
|
54
128
|
`<ase-session-id>${sessionId}</ase-session-id>\n` +
|
|
55
129
|
"\n" + md;
|
|
130
|
+
/* expand all @<file> references manually */
|
|
131
|
+
md = this.expandReferences(md, path.dirname(fileMd));
|
|
132
|
+
fs.writeFileSync("/tmp/xxx", md, "utf8");
|
|
56
133
|
/* inject markdown into session context */
|
|
57
134
|
process.stdout.write(JSON.stringify({
|
|
58
135
|
"hookSpecificOutput": {
|
|
@@ -110,8 +187,8 @@ export default class HookCommand {
|
|
|
110
187
|
hookCmd
|
|
111
188
|
.command("session-start")
|
|
112
189
|
.description("handle Claude Code SessionStart hook event")
|
|
113
|
-
.action(() => {
|
|
114
|
-
process.exit(this.doSessionStart());
|
|
190
|
+
.action(async () => {
|
|
191
|
+
process.exit(await this.doSessionStart());
|
|
115
192
|
});
|
|
116
193
|
/* register CLI sub-command "ase hook pre-tool-use" */
|
|
117
194
|
hookCmd
|
package/dst/ase-setup.js
CHANGED
|
@@ -6,28 +6,42 @@
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { execa } from "execa";
|
|
9
|
-
import
|
|
9
|
+
import which from "which";
|
|
10
|
+
import Version from "./ase-version.js";
|
|
10
11
|
/* CLI command "ase setup" */
|
|
11
12
|
export default class SetupCommand {
|
|
12
13
|
log;
|
|
13
14
|
constructor(log) {
|
|
14
15
|
this.log = log;
|
|
15
16
|
}
|
|
16
|
-
/*
|
|
17
|
-
async
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/* ensure a tool is available */
|
|
18
|
+
async ensureTool(tool) {
|
|
19
|
+
return which(tool).catch(() => {
|
|
20
|
+
throw new Error(`mandatory tool "${tool}" not found in $PATH`);
|
|
21
|
+
});
|
|
21
22
|
}
|
|
22
|
-
/*
|
|
23
|
-
async
|
|
24
|
-
this.log.write("info", `setup:
|
|
23
|
+
/* run a sub-process, suppressing output on success and emitting it on failure */
|
|
24
|
+
async run(cmd, args, cwd) {
|
|
25
|
+
this.log.write("info", `setup: $ ${cmd} ${args.join(" ")}` +
|
|
25
26
|
(cwd !== undefined ? ` (cwd: ${cwd})` : ""));
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
await execa(cmd, args, { stdio: "pipe", cwd }).catch((err) => {
|
|
28
|
+
const exitCode = typeof err?.exitCode === "number" ? err.exitCode : -1;
|
|
29
|
+
this.log.write("error", `setup: command failed: exit code: ${exitCode}`);
|
|
30
|
+
if (typeof err?.stdout === "string" && err.stdout.length > 0) {
|
|
31
|
+
this.log.write("error", "setup: command failed: stdout:");
|
|
32
|
+
process.stdout.write(err.stdout);
|
|
33
|
+
}
|
|
34
|
+
if (typeof err?.stderr === "string" && err.stderr.length > 0) {
|
|
35
|
+
this.log.write("error", "setup: command failed: stderr:");
|
|
36
|
+
process.stderr.write(err.stderr);
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
});
|
|
28
40
|
}
|
|
29
41
|
/* handler for "ase setup install" */
|
|
30
42
|
async doInstall(dev) {
|
|
43
|
+
await this.ensureTool("npm");
|
|
44
|
+
await this.ensureTool("claude");
|
|
31
45
|
this.log.write("info", `setup: install${dev ? "[dev]" : ""}: ` +
|
|
32
46
|
`installing ASE Claude Code plugin (origin: ${dev ? "local" : "remote"})`);
|
|
33
47
|
const source = dev ? process.cwd() : "rse/ase";
|
|
@@ -37,6 +51,8 @@ export default class SetupCommand {
|
|
|
37
51
|
}
|
|
38
52
|
/* handler for "ase setup update" */
|
|
39
53
|
async doUpdate(force, dev) {
|
|
54
|
+
await this.ensureTool("npm");
|
|
55
|
+
await this.ensureTool("claude");
|
|
40
56
|
if (dev) {
|
|
41
57
|
/* update ASE CLI Tool */
|
|
42
58
|
this.log.write("info", "setup: update[dev]: re-build ASE CLI tool (origin: local)");
|
|
@@ -52,15 +68,8 @@ export default class SetupCommand {
|
|
|
52
68
|
}
|
|
53
69
|
else {
|
|
54
70
|
/* perform NPM version check */
|
|
55
|
-
const current =
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
latest = await this.capture("npm", ["view", "@rse/ase", "version"]);
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
62
|
-
this.log.write("warning", `setup: update: failed to query latest ASE version: ${message}`);
|
|
63
|
-
}
|
|
71
|
+
const current = Version.current();
|
|
72
|
+
const latest = await Version.latest();
|
|
64
73
|
if (!force && latest !== "" && latest === current) {
|
|
65
74
|
this.log.write("info", `setup: update: ASE already at latest version ${current}`);
|
|
66
75
|
return 0;
|
|
@@ -77,6 +86,8 @@ export default class SetupCommand {
|
|
|
77
86
|
}
|
|
78
87
|
/* handler for "ase setup uninstall" */
|
|
79
88
|
async doUninstall(dev) {
|
|
89
|
+
await this.ensureTool("npm");
|
|
90
|
+
await this.ensureTool("claude");
|
|
80
91
|
/* uninstall ASE Claude Code plugin */
|
|
81
92
|
this.log.write("info", `setup: uninstall${dev ? "[dev]" : ""}: ` +
|
|
82
93
|
`uninstalling ASE Claude Code plugin (origin: ${dev ? "local" : "remote"})`);
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
import { execa } from "execa";
|
|
7
|
+
import pkg from "../package.json" with { type: "json" };
|
|
8
|
+
/* determination of current and available ASE versions */
|
|
9
|
+
export default class Version {
|
|
10
|
+
/* return current ASE version */
|
|
11
|
+
static current() {
|
|
12
|
+
return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
/* return latest ASE version available on the NPM registry */
|
|
15
|
+
static async latest() {
|
|
16
|
+
let latest = "";
|
|
17
|
+
try {
|
|
18
|
+
const r = await execa("npm", ["view", "@rse/ase", "version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
19
|
+
latest = r.stdout.trim();
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
throw new Error(`failed to query latest ASE version: ${message}`, { cause: err });
|
|
24
|
+
}
|
|
25
|
+
return latest;
|
|
26
|
+
}
|
|
27
|
+
}
|
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.0.
|
|
9
|
+
"version": "0.0.17",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"shx": "0.4.0",
|
|
33
33
|
|
|
34
34
|
"@types/node": "25.6.0",
|
|
35
|
-
"@types/luxon": "3.7.1"
|
|
35
|
+
"@types/luxon": "3.7.1",
|
|
36
|
+
"@types/which": "3.0.4"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"commander": "14.0.3",
|
|
@@ -48,7 +49,8 @@
|
|
|
48
49
|
"pretty-ms": "9.3.0",
|
|
49
50
|
"luxon": "3.7.2",
|
|
50
51
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
51
|
-
"zod": "4.4.2"
|
|
52
|
+
"zod": "4.4.2",
|
|
53
|
+
"which": "6.0.1"
|
|
52
54
|
},
|
|
53
55
|
"engines": {
|
|
54
56
|
"npm": ">=10.0.0",
|