@rse/ase 0.0.37 → 0.0.38

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-hello.js CHANGED
@@ -4,9 +4,9 @@
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
6
  import { Chalk } from "chalk";
7
- /* forced-color chalk instance: stdout may be a pipe, so chalk
8
- auto-detection would yield level 0; force level 1 to keep
9
- emitting ANSI sequences as in "ase statusline" */
7
+ /* forced-color chalk instance: stdout is a pipe under Claude Code,
8
+ so chalk auto-detection would yield level 0; force level 1 to keep
9
+ emitting ANSI sequences as the original implementation did */
10
10
  const c = new Chalk({ level: 1 });
11
11
  /* command-line handling */
12
12
  export default class HelloCommand {
@@ -20,7 +20,7 @@ export default class HelloCommand {
20
20
  .command("hello")
21
21
  .description("print a colored greeting message")
22
22
  .option("-s, --subject <name>", "subject to greet", "Universe")
23
- .action((opts) => {
23
+ .action(async (opts) => {
24
24
  process.stdout.write(`${c.red(`${opts.subject} World!`)}\n`);
25
25
  });
26
26
  }
package/dst/ase-setup.js CHANGED
@@ -3,6 +3,7 @@
3
3
  ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
+ import fs from "node:fs/promises";
6
7
  import path from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { execa } from "execa";
@@ -24,6 +25,74 @@ export default class SetupCommand {
24
25
  throw new Error(`mandatory tool "${tool}" not found in $PATH`);
25
26
  });
26
27
  }
28
+ /* determine whether a global "npm" operation requires "sudo" by
29
+ checking whether the npm global install root is writable by the
30
+ current user; on Windows or when already running as root, no
31
+ elevation is needed */
32
+ async npmGlobalNeedsSudo() {
33
+ /* Windows has no "sudo" concept here */
34
+ if (process.platform === "win32")
35
+ return false;
36
+ /* already running as root */
37
+ const getuid = process.getuid;
38
+ if (typeof getuid === "function" && getuid.call(process) === 0)
39
+ return false;
40
+ /* determine the npm global prefix and probe writability of the
41
+ directories that "npm -g" actually mutates */
42
+ let prefix = "";
43
+ try {
44
+ const result = await execa("npm", ["prefix", "-g"], { stdio: "pipe" });
45
+ prefix = result.stdout.trim();
46
+ }
47
+ catch {
48
+ /* if we cannot determine the prefix, fall back to "no sudo" */
49
+ return false;
50
+ }
51
+ if (prefix === "")
52
+ return false;
53
+ const candidates = [
54
+ prefix,
55
+ path.join(prefix, "bin"),
56
+ path.join(prefix, "lib", "node_modules")
57
+ ];
58
+ for (const dir of candidates) {
59
+ try {
60
+ await fs.access(dir, fs.constants.W_OK);
61
+ }
62
+ catch {
63
+ /* directory exists but not writable, or does not exist
64
+ inside a non-writable parent: require sudo */
65
+ try {
66
+ await fs.access(dir, fs.constants.F_OK);
67
+ return true;
68
+ }
69
+ catch {
70
+ /* directory does not exist: check parent writability */
71
+ try {
72
+ await fs.access(path.dirname(dir), fs.constants.W_OK);
73
+ }
74
+ catch {
75
+ return true;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+ /* build the (cmd, args) pair for an "npm" invocation, prefixing
83
+ with "sudo" when necessary for global operations */
84
+ async npmCmd(args, global) {
85
+ if (global && await this.npmGlobalNeedsSudo()) {
86
+ const sudo = await which("sudo").catch(() => "");
87
+ if (sudo !== "") {
88
+ this.log.write("info", "setup: npm global install root not writable: using \"sudo\"");
89
+ return { cmd: "sudo", args: ["npm", ...args] };
90
+ }
91
+ this.log.write("warning", "setup: npm global install root is not writable by current user " +
92
+ "and \"sudo\" not found in $PATH: attempting without elevation");
93
+ }
94
+ return { cmd: "npm", args };
95
+ }
27
96
  /* run a sub-process, suppressing output on success and emitting it on failure */
28
97
  async run(cmd, args, opts = {}) {
29
98
  const { cwd, quiet = false, retries = 1, ignoreError } = opts;
@@ -118,7 +187,8 @@ export default class SetupCommand {
118
187
  }
119
188
  /* update ASE CLI tool */
120
189
  this.log.write("info", `setup: update: updating ASE CLI tool: ${current} -> ${latest}`);
121
- await this.run("npm", ["update", "-g", "@rse/ase"]);
190
+ const updateCmd = await this.npmCmd(["update", "-g", "@rse/ase"], true);
191
+ await this.run(updateCmd.cmd, updateCmd.args);
122
192
  /* update ASE plugin */
123
193
  this.log.write("info", `setup: update: updating ASE ${spec.label} plugin`);
124
194
  await this.run(spec.cli, ["plugin", "marketplace", "update", "ase"]);
@@ -165,7 +235,8 @@ export default class SetupCommand {
165
235
  /* uninstall ASE CLI tool (non-development only) */
166
236
  if (!dev) {
167
237
  this.log.write("info", "setup: uninstall: uninstalling ASE CLI tool (origin: remote)");
168
- await this.run("npm", ["uninstall", "-g", "@rse/ase"]);
238
+ const uninstallCmd = await this.npmCmd(["uninstall", "-g", "@rse/ase"], true);
239
+ await this.run(uninstallCmd.cmd, uninstallCmd.args);
169
240
  }
170
241
  return 0;
171
242
  }
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.37",
9
+ "version": "0.0.38",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -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.37",
9
+ "version": "0.0.38",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -84,19 +84,30 @@ permitted way to persist artifacts is via `task_save(...)`.
84
84
 
85
85
  Then set <feature/> to the response of the user.
86
86
 
87
- 4. Report the task and feature with the following <template/>:
87
+ 4. <if condition="
88
+ <ase-task-id/> is equal `default` and
89
+ <feature/> is not empty
90
+ ">
91
+ Set <ase-task-id/> to a unique task id, derived from <feature/>,
92
+ which consists of two lower-case words concatenated with a
93
+ `-` character. Then call the `task_id(id: <ase-task-id/>,
94
+ session: <ase-session-id/>)` tool from the `ase` MCP service to
95
+ implicitly switch the task.
96
+ </if>
97
+
98
+ 5. Report the task and feature with the following <template/>:
88
99
 
89
100
  <template>
90
101
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**
91
102
  ⧉ **ASE**: ⇌ feature: **<feature/>**
92
103
  </template>
93
104
 
94
- 5. Figure out what the requested <feature/> to be crafted is about.
105
+ 6. Figure out what the requested <feature/> to be crafted is about.
95
106
 
96
- 6. Ask the user for clarification if the goal of this crafting is too
107
+ 7. Ask the user for clarification if the goal of this crafting is too
97
108
  unclear.
98
109
 
99
- 7. Do not output anything in this step, except you asked the user.
110
+ 8. Do not output anything in this step, except you asked the user.
100
111
 
101
112
  2. **Investigate Code Base**:
102
113
 
@@ -84,19 +84,30 @@ permitted way to persist artifacts is via `task_save(...)`.
84
84
 
85
85
  Then set <request/> to the response of the user.
86
86
 
87
- 4. Report the task and request with the following <template/>:
87
+ 4. <if condition="
88
+ <ase-task-id/> is equal `default` and
89
+ <request/> is not empty
90
+ ">
91
+ Set <ase-task-id/> to a unique task id, derived from <request/>,
92
+ which consists of two lower-case words concatenated with a
93
+ `-` character. Then call the `task_id(id: <ase-task-id/>,
94
+ session: <ase-session-id/>)` tool from the `ase` MCP service to
95
+ implicitly switch the task.
96
+ </if>
97
+
98
+ 5. Report the task and request with the following <template/>:
88
99
 
89
100
  <template>
90
101
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**
91
102
  ⧉ **ASE**: ⇌ request: **<request/>**
92
103
  </template>
93
104
 
94
- 5. Figure out what the artifact refactoring <request/> is about.
105
+ 6. Figure out what the artifact refactoring <request/> is about.
95
106
 
96
- 6. Ask the user for clarification if the goal of this refactoring is
107
+ 7. Ask the user for clarification if the goal of this refactoring is
97
108
  too unclear.
98
109
 
99
- 7. Do not output anything in this step, except you asked the user.
110
+ 8. Do not output anything in this step, except you asked the user.
100
111
 
101
112
  2. **Investigate Code Base**:
102
113
 
@@ -56,12 +56,13 @@ permitted way to persist artifacts is via `task_save(...)`.
56
56
  1. **Reason About Problem**:
57
57
 
58
58
  1. If <problem/> matches the regexp `^[PT]\d+$` (i.e. a bare issue
59
- identifier like `P1`, `P2`, `T1`, `T2`, ...), set
60
- <problem-id><problem/></problem-id>, call the `task_id(id:
61
- <ase-task-id/>, session: <ase-session-id/>)` tool from the `ase`
62
- MCP service to implicitly switch the task, and then call the
63
- `kv_get(key: "ase-issue-<problem-id/>")` tool
64
- of the `ase` MCP service to retrieve the previously persisted
59
+ identifier like `P1`, `P2`, `T1`, `T2`, ...),
60
+ set <problem-id><problem/></problem-id> and
61
+ <ase-task-id><problem/></ase-task-id>, call the `task_id(id:
62
+ <ase-task-id/>, session: <ase-session-id/>)` tool from the
63
+ `ase` MCP service to implicitly switch the task, and then
64
+ call the `kv_get(key: "ase-issue-<problem-id/>")` tool of
65
+ the `ase` MCP service to retrieve the previously persisted
65
66
  problem description. If the returned `text` is non-empty, set
66
67
  <problem><text/></problem>, otherwise complain to the user that
67
68
  no analyzer result exists for <problem-id/> and stop processing.
@@ -95,21 +96,32 @@ permitted way to persist artifacts is via `task_save(...)`.
95
96
 
96
97
  Then set <problem/> to the response of the user.
97
98
 
98
- 5. Report the task and problem with the following <template/>:
99
+ 5. <if condition="
100
+ <ase-task-id/> is equal `default` and
101
+ <problem/> is not empty
102
+ ">
103
+ Set <ase-task-id/> to a unique task id, derived from <problem/>,
104
+ which consists of two lower-case words concatenated with a
105
+ `-` character. Then call the `task_id(id: <ase-task-id/>,
106
+ session: <ase-session-id/>)` tool from the `ase` MCP service to
107
+ implicitly switch the task.
108
+ </if>
109
+
110
+ 6. Report the task and problem with the following <template/>:
99
111
 
100
112
  <template>
101
113
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**
102
114
  ⧉ **ASE**: ⇌ problem: **<problem/>**
103
115
  </template>
104
116
 
105
- 6. Figure out what the requested <problem/> is about.
117
+ 7. Figure out what the requested <problem/> is about.
106
118
 
107
- 7. Ask the user for clarification if the goal of this resolution is
119
+ 8. Ask the user for clarification if the goal of this resolution is
108
120
  too unclear.
109
121
 
110
- 8. Do not output anything in this step, except you asked the user.
122
+ 9. Do not output anything in this step, except you asked the user.
111
123
 
112
- 9. Investigate and *figure out details* related to this problem.
124
+ 10. Investigate and *figure out details* related to this problem.
113
125
  Report those details with the following <template/>:
114
126
 
115
127
  <template>
@@ -21,8 +21,10 @@ Evaluate Alternatives
21
21
  Evaluate Alternatives
22
22
  </skill>
23
23
 
24
+ <role>
24
25
  Your role is an experienced, *expert-level assistant*,
25
26
  specialized in *evaluating alternatives*.
27
+ </role>
26
28
 
27
29
  <objective>
28
30
  *Evaluate* *alternatives* through a weighted
@@ -73,10 +75,11 @@ multi-*criteria* decision matrix.
73
75
  over) is its single most distinguishing perspective, and remember
74
76
  this as an <info-K/> (K=1-N) formatted like `<type/>: <hint/>` where
75
77
  <type/> is one of `USP`, `Crux`, or `Gotcha` and <hint/> is a 1-6
76
- word hint.
78
+ word hint. Do not output anything.
77
79
 
78
80
  - For the set of alternatives, decide what the 1-6 word long
79
81
  name of the *class of alternatives* <class-of-alternatives/> is.
82
+ Do not output anything.
80
83
 
81
84
  - For each alternative <alternative-K/> (K=1-N), decide whether
82
85
  it is a genuine member of <class-of-alternatives/>. If any
@@ -178,16 +181,16 @@ multi-*criteria* decision matrix.
178
181
  - The best alternative <alternative-K/> (K=1-N) is the alternative
179
182
  whose *raw, unrounded* <rating-K/> (i.e. the product-sum from STEP
180
183
  4, *before* the display-only rounding) is the maximum rating value
181
- across all alternatives.
184
+ across all alternatives. Do not output anything.
182
185
 
183
186
  - The second best alternative <alternative-X/> (X=1-N, X != K) is
184
187
  the alternative whose *raw, unrounded* <rating-X/> is the second
185
- largest rating value across all alternatives.
188
+ largest rating value across all alternatives. Do not output anything.
186
189
 
187
190
  - If multiple alternatives share the second-largest raw rating, pick
188
191
  any one of them as <alternative-X/>; the resulting <distance/> and
189
192
  <percentage/> are unaffected by the choice, so the downstream output
190
- is deterministic.
193
+ is deterministic. Do not output anything.
191
194
 
192
195
  - Determine rating distance <distance/> between <alternative-K/> and
193
196
  <alternative-X/> from their *raw, unrounded* ratings by calculating:
@@ -203,6 +206,7 @@ multi-*criteria* decision matrix.
203
206
  (so a true zero tie with <distance/> = 0 falls into the
204
207
  *MULTIPLE BEST* branch below, and a non-zero gap with zero
205
208
  best falls into the *small distance* branch below).
209
+ Do not output anything.
206
210
 
207
211
  - By construction, <rating-K/> is the maximum rating across
208
212
  all alternatives, so <distance/> >= 0 always holds; using
@@ -210,7 +214,7 @@ multi-*criteria* decision matrix.
210
214
  regimes. Note that when <rating-K/> itself is negative, the
211
215
  denominator anchors to a poor best rating and small gaps can
212
216
  appear large; the all-negative regime is surfaced as a dedicated
213
- warning branch below.
217
+ warning branch below. Do not output anything.
214
218
 
215
219
  - If <percentage/> is less than 0.01 (i.e. <distance/> is
216
220
  effectively zero relative to abs(<rating-K/>)), stop the flow after