@rse/ase 0.9.56 → 0.9.57

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 CHANGED
@@ -124,27 +124,33 @@ const gitToplevel = () => {
124
124
  gitToplevelCache.set(cwd, top);
125
125
  return top === "" ? null : top;
126
126
  };
127
- /* detect whether a project context exists, i.e. either we are inside
128
- a Git working tree or a ".ase" directory is present at or above cwd */
129
- const hasProjectContext = () => {
130
- if (gitToplevel() !== null)
131
- return true;
127
+ /* determine the project root directory, i.e. either the top-level
128
+ directory of the Git working tree or the nearest directory at or
129
+ above cwd which carries a ".ase" directory */
130
+ const projectRoot = () => {
131
+ const top = gitToplevel();
132
+ if (top !== null)
133
+ return top;
132
134
  let dir = fs.realpathSync(process.cwd());
133
135
  for (;;) {
134
136
  if (fs.existsSync(path.join(dir, ".ase")))
135
- return true;
137
+ return dir;
136
138
  const parent = path.dirname(dir);
137
139
  if (parent === dir)
138
- return false;
140
+ return null;
139
141
  dir = parent;
140
142
  }
141
143
  };
144
+ /* detect whether a project context exists, i.e. either we are inside
145
+ a Git working tree or a ".ase" directory is present at or above cwd */
146
+ const hasProjectContext = () => projectRoot() !== null;
142
147
  /* parse a raw "--scope" option value into a canonical Scope chain;
143
148
  accepts a comma-separated list of terms in any order. The "user"
144
149
  term is always implicitly added at the bottom of the chain; the
145
150
  "project" term is implicitly added only when a project context
146
- exists (Git repository or ".ase" directory at or above cwd), and
147
- an explicit "project" term requires that same context */
151
+ exists (Git repository or ".ase" directory at or above cwd) and it
152
+ stays weaker than the strongest explicitly requested term, and an
153
+ explicit "project" term requires that same context */
148
154
  export const parseScope = (value) => {
149
155
  const projectActive = hasProjectContext();
150
156
  const input = (value === undefined || value === "") ?
@@ -162,7 +168,11 @@ export const parseScope = (value) => {
162
168
  if (seen.has("project") && !projectActive)
163
169
  throw new Error("invalid --scope: \"project\" requires a project context " +
164
170
  "(a Git repository or a \".ase\" directory at or above the current directory)");
165
- if (!seen.has("project") && projectActive)
171
+ /* the strongest term of the chain is the write target, so an implicitly
172
+ added "project" term must never outrank the strongest explicitly
173
+ requested term, as this would silently retarget the caller's request */
174
+ const rankMax = Math.max(...terms.map((t) => scopeRank(t.kind)));
175
+ if (!seen.has("project") && projectActive && rankMax > scopeRank("project"))
166
176
  terms.unshift({ kind: "project" });
167
177
  if (!seen.has("user"))
168
178
  terms.unshift({ kind: "user" });
@@ -210,6 +220,7 @@ export class Config {
210
220
  log;
211
221
  docs;
212
222
  target;
223
+ pruned;
213
224
  /* creation */
214
225
  constructor(name, schema, log, scope = [{ kind: "user" }, { kind: "project" }]) {
215
226
  if (scope.length === 0)
@@ -222,6 +233,7 @@ export class Config {
222
233
  this.filename = this.resolveFilename(name, tgt);
223
234
  this.docs = [{ scope: tgt, filename: this.filename, doc: new Document() }];
224
235
  this.target = 0;
236
+ this.pruned = [];
225
237
  }
226
238
  /* render a scope term as a short textual label */
227
239
  static scopeLabel(term) {
@@ -252,16 +264,13 @@ export class Config {
252
264
  return path.join(this.userConfigDir(), `${name}.yaml`);
253
265
  else if (term.kind === "project") {
254
266
  const rel = path.join(".ase", `${name}.yaml`);
255
- const cwd = process.cwd();
256
- const top = gitToplevel();
257
- const found = top !== null ?
258
- this.findUpward(cwd, top, rel) :
259
- (fs.existsSync(path.join(cwd, rel)) ? path.join(cwd, rel) : null);
260
- return found ?? path.join(top ?? cwd, rel);
267
+ const root = projectRoot() ?? process.cwd();
268
+ const found = this.findUpward(process.cwd(), root, rel);
269
+ return found ?? path.join(root, rel);
261
270
  }
262
271
  else if (term.kind === "task") {
263
- const top = gitToplevel() ?? process.cwd();
264
- return path.join(top, ".ase", "task", term.id, `${name}.yaml`);
272
+ const root = projectRoot() ?? process.cwd();
273
+ return path.join(root, ".ase", "task", term.id, `${name}.yaml`);
265
274
  }
266
275
  else
267
276
  return path.join(os.homedir(), ".ase", "session", term.id, `${name}.yaml`);
@@ -290,6 +299,7 @@ export class Config {
290
299
  read(mode = "lenient") {
291
300
  const chain = this.scope;
292
301
  const docs = [];
302
+ this.pruned = [];
293
303
  for (let i = 0; i < chain.length; i++) {
294
304
  const sc = chain[i];
295
305
  if (sc.kind === "default") {
@@ -321,6 +331,8 @@ export class Config {
321
331
  if (perDocMode === "strict")
322
332
  throw new Error(msg);
323
333
  this.log.write("warning", msg);
334
+ if (isTarget)
335
+ this.pruned.push(`unparsable YAML (${doc.errors[0].message.split("\n")[0]})`);
324
336
  doc = new Document();
325
337
  }
326
338
  docs.push({ scope: sc, filename, doc });
@@ -330,7 +342,9 @@ export class Config {
330
342
  for (let i = 0; i < docs.length; i++) {
331
343
  const isTarget = (i === this.target);
332
344
  const perDocMode = isTarget ? mode : "lenient";
333
- this.validateDoc(docs[i].doc, docs[i].filename, perDocMode);
345
+ const removed = this.validateDoc(docs[i].doc, docs[i].filename, perDocMode);
346
+ if (isTarget)
347
+ this.pruned.push(...removed);
334
348
  }
335
349
  }
336
350
  /* acquire a cross-process advisory lock on the target scope's file,
@@ -355,18 +369,28 @@ export class Config {
355
369
  const td = this.docs[this.target];
356
370
  if (td.scope.kind === "default")
357
371
  throw new Error("internal error: \"default\" scope is not writable");
372
+ /* a lenient read physically removes the invalid content from the in-memory
373
+ target document, so writing that document back would silently erase the
374
+ very same content from the file on disk */
375
+ if (this.pruned.length > 0)
376
+ throw new Error(`refusing to overwrite ${td.filename}: it carries invalid content ` +
377
+ `(${this.pruned.join(", ")}) which was skipped on reading and hence would be ` +
378
+ "lost on writing -- repair the file first");
358
379
  this.validateDoc(td.doc, td.filename, "strict");
359
380
  fs.mkdirSync(path.dirname(td.filename), { recursive: true });
360
381
  writeFileAtomic.sync(td.filename, td.doc.toString({ indent: 4 }), { encoding: "utf8" });
361
382
  }
362
- /* validate a single YAML document against the optional schema */
383
+ /* validate a single YAML document against the optional schema; in "strict"
384
+ mode all invalid entries are reported as a thrown error, in "lenient" mode
385
+ they are removed from the document and their dotted paths are returned */
363
386
  validateDoc(doc, filename, mode = "strict") {
364
387
  if (this.schema === null)
365
- return;
388
+ return [];
389
+ const removed = [];
366
390
  for (;;) {
367
391
  const result = v.safeParse(this.schema, doc.toJS());
368
392
  if (result.success)
369
- return;
393
+ return removed;
370
394
  if (mode === "strict") {
371
395
  const issues = result.issues.map((i) => {
372
396
  const dotPath = (i.path ?? []).map((p) => String(p.key)).join(".");
@@ -374,19 +398,20 @@ export class Config {
374
398
  }).join("; ");
375
399
  throw new Error(`invalid configuration in ${filename}: ${issues}`);
376
400
  }
377
- let progressed = false;
401
+ const before = removed.length;
378
402
  for (const i of result.issues) {
379
403
  const segs = (i.path ?? []).map((p) => String(p.key));
380
404
  const dotPath = segs.join(".");
381
405
  this.log.write("warning", `invalid entry in ${filename}: ${dotPath ? `${dotPath}: ` : ""}${i.message}`);
382
- if (segs.length > 0) {
383
- doc.deleteIn(segs);
384
- progressed = true;
385
- }
386
- /* root-level issues cannot be deleted; processing continues with the remaining issues */
406
+ if (segs.length > 0 && doc.deleteIn(segs))
407
+ removed.push(dotPath);
408
+ /* issues at the document root and issues whose stringified path does not
409
+ address a deletable node (e.g. a non-string YAML key like "404:", which
410
+ "toJS" stringifies) cannot be removed; processing continues with the
411
+ remaining issues */
387
412
  }
388
- if (!progressed)
389
- return;
413
+ if (removed.length === before)
414
+ return removed;
390
415
  }
391
416
  }
392
417
  /* enumerate all full dotted leaf paths from the attached valibot schema */
@@ -444,6 +469,21 @@ export class Config {
444
469
  }
445
470
  return undefined;
446
471
  }
472
+ /* retrieve the explicitly configured value at a dotted key, i.e. the same
473
+ cascade as "get", but skipping the built-in "default" scope layer, so
474
+ callers can distinguish a deliberately configured value from a merely
475
+ preset one */
476
+ getExplicit(key) {
477
+ const segs = this.resolveKey(key).split(".");
478
+ for (let i = this.docs.length - 1; i >= 0; i--) {
479
+ if (this.docs[i].scope.kind === "default")
480
+ continue;
481
+ const node = this.docs[i].doc.getIn(segs);
482
+ if (node !== undefined)
483
+ return node;
484
+ }
485
+ return undefined;
486
+ }
447
487
  /* enumerate the effective leaf entries across the full scope chain;
448
488
  each returned entry identifies the originating scope */
449
489
  entries() {
@@ -551,7 +591,8 @@ export default class ConfigCommand {
551
591
  .option("--scope <scope>", "configuration scope chain: comma-separated list of \"user\", \"project\", " +
552
592
  "\"task:<id>\", and/or \"session:<id>\" terms (e.g. \"task:N,session:M\"); " +
553
593
  "\"user\" is always implicitly included and \"project\" is implicitly " +
554
- "included whenever a project context (Git repo or upward \".ase\" directory) exists")
594
+ "included whenever a project context (Git repo or upward \".ase\" directory) exists, " +
595
+ "but never above the strongest explicitly requested term")
555
596
  .description("manage ASE configuration")
556
597
  .action(() => {
557
598
  configCmd.outputHelp();
package/dst/ase-getopt.js CHANGED
@@ -7,7 +7,7 @@ import { z } from "zod";
7
7
  import { Command, Option } from "commander";
8
8
  import { parse as shParse, quote as shQuote } from "shell-quote";
9
9
  /* tokenize a raw input string into [start,end) token ranges, preserving
10
- the quoting so the original text can later be sliced verbatim */
10
+ the quoting and escaping so the original text can later be sliced verbatim */
11
11
  const tokenizeRanges = (input) => {
12
12
  const ranges = [];
13
13
  let i = 0;
@@ -30,6 +30,8 @@ const tokenizeRanges = (input) => {
30
30
  if (i < input.length)
31
31
  i++;
32
32
  }
33
+ else if (ch === "\\" && i + 1 < input.length)
34
+ i += 2;
33
35
  else
34
36
  i++;
35
37
  }
package/dst/ase-hook.js CHANGED
@@ -240,10 +240,12 @@ export default class HookCommand {
240
240
  const projectId = path.basename(projectDir);
241
241
  /* determine user id */
242
242
  const userId = process.env.USER ?? process.env.LOGNAME ?? "unknown";
243
- /* helper function: determine a setting from the configuration,
244
- falling back to an environment variable and a default */
243
+ /* helper function: determine a setting from the explicitly configured
244
+ scopes, falling back to an environment variable and a default; the
245
+ built-in "default" scope layer is deliberately skipped, as its preset
246
+ value is always present and would hence shadow the environment variable */
245
247
  const setting = (key, envVar, dflt) => {
246
- const val = cfg.get(key);
248
+ const val = cfg.getExplicit(key);
247
249
  return typeof val === "string" ? val : (process.env[envVar] ?? dflt);
248
250
  };
249
251
  /* determine agent persona style, agent guidance level, and project boxing transparency */
@@ -19,9 +19,12 @@ export class Markdown {
19
19
  /* segment the input line-wise into alternating non-fenced and
20
20
  fenced regions: a fenced code block opens with a line whose first
21
21
  non-whitespace content is a run of 3+ backticks or tildes and
22
- closes with a matching-or-longer run of the same marker; fenced
23
- regions are emitted verbatim while only non-fenced regions are
24
- handed to the rewriting passes below */
22
+ closes with a matching-or-longer run of the same marker; for a
23
+ backtick fence the info string trailing the run must not carry any
24
+ further backtick, so an inline code span delimited by a run of 3+
25
+ backticks never opens a fence; fenced regions are emitted verbatim
26
+ while only non-fenced regions are handed to the rewriting passes
27
+ below */
25
28
  const lines = text.split("\n");
26
29
  let result = "";
27
30
  let buf = "";
@@ -37,7 +40,7 @@ export class Markdown {
37
40
  for (let li = 0; li < lines.length; li++) {
38
41
  const line = lines[li];
39
42
  const nl = li < lines.length - 1 ? "\n" : "";
40
- const m = line.match(/^\s*(`{3,}|~{3,})/);
43
+ const m = line.match(/^\s*(`{3,}(?![^`]*`)|~{3,})/);
41
44
  if (!inFence && m) {
42
45
  /* a fence-opening line: flush the pending non-fenced buffer,
43
46
  enter fenced mode, and emit the opener verbatim */
@@ -439,7 +439,7 @@ export default class ServiceCommand {
439
439
  return await new Promise(() => { });
440
440
  }
441
441
  if (port !== null) {
442
- const match = await probe(port, ctx.projectId);
442
+ const match = await probe(port, ctx.projectId).catch(() => null);
443
443
  if (match === true) {
444
444
  this.log.write("info", `service: already running on port ${port}`);
445
445
  return 0;
package/dst/ase-task.js CHANGED
@@ -334,6 +334,7 @@ export class Task {
334
334
  /* set the active task id for a given session */
335
335
  static setId(log, session, id) {
336
336
  Task.validateSession(session);
337
+ Task.validateId(id);
337
338
  const scope = parseScope(`session:${session}`);
338
339
  const cfg = new Config("config", configSchema, log, scope);
339
340
  cfg.lock(() => {
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "homepage": "https://ase.tools",
7
7
  "repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
8
8
  "bugs": { "url": "https://github.com/rse/ase/issues" },
9
- "version": "0.9.56",
9
+ "version": "0.9.57",
10
10
  "license": "Apache-2.0",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "0.9.56",
3
+ "version": "0.9.57",
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.9.56",
3
+ "version": "0.9.57",
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.9.56",
3
+ "version": "0.9.57",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -194,4 +194,8 @@ Set <args>--int-reuse-task</args>.
194
194
  Then call the tool `Skill(skill: "ase:ase-task-edit", args: "<args/>")`.
195
195
  </else>
196
196
 
197
+ 6. In every branch above which invoked the `Skill` tool, you *MUST*
198
+ immediately stop processing the current skill once the `Skill`
199
+ tool was used.
200
+
197
201
  </define>
@@ -156,3 +156,20 @@ related to this MCP call except the following <template/>:
156
156
  </expand>
157
157
 
158
158
  </define>
159
+
160
+ <define name="task-next-handoff">
161
+
162
+ Set <args>--int-reuse-task</args>.
163
+ <if condition="<getopt-option-next/> is not equal `none`">
164
+ Set <args><args/> --next <getopt-option-next/></args>
165
+ </if>
166
+ Only output the following <template/> and then call the tool
167
+ `Skill(skill: "ase:<arg1/>", args: "<args/>")` to invoke the
168
+ `ase:<arg1/>` skill to continue with the updated plan. Immediately
169
+ stop processing the current skill once the `Skill` tool was used.
170
+
171
+ <template>
172
+ ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **<arg2/>**
173
+ </template>
174
+
175
+ </define>
@@ -18,20 +18,6 @@ at `01`) according to the order of the **Artifact** list below, and with
18
18
  Pascal-casing each word (upper-casing its first letter) and using `-`
19
19
  characters instead of spaces (e.g. `Context-View`).
20
20
 
21
- Each **Artifact** file *MUST* begin with a single blank line before its
22
- `#` heading and end with a single blank line after its last content line
23
- (followed by the trailing newline), mirroring the blank lines shown
24
- inside the `<format>` blocks below.
25
-
26
- Each **Artifact** contains two timestamps: the <timestamp-created/>
27
- is the timestamp when this **Artifact** was created. The
28
- <timestamp-modified/> is the timestamp when this **Artifact** was last
29
- modified. Both use an ISO-style format value. The value of both can be
30
- determined by a call to the `ase_timestamp(format: "yyyy-LL-dd HH:mm")`
31
- tool of the `ase` MCP server, using the `text` field of its response.
32
- Whenever an **Artifact** is updated, the <timestamp-modified/> *MUST* be
33
- updated, too.
34
-
35
21
  The **Artifact Set** **Architecture (ARCH)** consists of the following
36
22
  distinct **Artifact**s (listed under their <artifact-name/> and their
37
23
  <artifact-id/>):
@@ -51,8 +51,7 @@ Artifact Meta Information
51
51
  name (for stable ordering) and is *not* part of the **Artifact**'s
52
52
  or its **Aspect**s' identifiers.
53
53
 
54
- Each **Artifact** *MUST* have an initial blank line and a trailing
55
- blank line. All its lines should be kept below 140 characters,
54
+ All lines of an **Artifact** should be kept below 140 characters,
56
55
  whenever possible by line-breaking with newlines after about 120
57
56
  characters per line.
58
57
 
@@ -64,6 +63,20 @@ Artifact Meta Information
64
63
  characters and in total not longer than 30 characters). An example
65
64
  is `user-login`.
66
65
 
66
+ Each **Artifact** file *MUST* begin with a single blank line before its
67
+ `#` heading and end with a single blank line after its last content line
68
+ (followed by the trailing newline), mirroring the blank lines shown
69
+ inside the `<format>` blocks below.
70
+
71
+ Each **Artifact** contains two timestamps: the <timestamp-created/>
72
+ is the timestamp when this **Artifact** was created. The
73
+ <timestamp-modified/> is the timestamp when this **Artifact** was last
74
+ modified. Both use an ISO-style format value. The value of both can be
75
+ determined by a call to the `ase_timestamp(format: "yyyy-LL-dd HH:mm")`
76
+ tool of the `ase` MCP server, using the `text` field of its response.
77
+ Whenever an **Artifact** is updated, the <timestamp-modified/> *MUST* be
78
+ updated, too.
79
+
67
80
  An **Artifact** *MAY* additionally declare an **Export** -- a derived,
68
81
  ready-to-consume rendering of (part of) its content, materialized as a
69
82
  *side-by-side* file next to the **Artifact** itself. An **Artifact**
@@ -18,20 +18,6 @@ at `01`) according to the order of the **Artifact** list below, and with
18
18
  Pascal-casing each word (upper-casing its first letter) and using `-`
19
19
  characters instead of spaces (e.g. `Customer-Journey`).
20
20
 
21
- Each **Artifact** file *MUST* begin with a single blank line before its
22
- `#` heading and end with a single blank line after its last content line
23
- (followed by the trailing newline), mirroring the blank lines shown
24
- inside the `<format>` blocks below.
25
-
26
- Each **Artifact** contains two timestamps: the <timestamp-created/>
27
- is the timestamp when this **Artifact** was created. The
28
- <timestamp-modified/> is the timestamp when this **Artifact** was last
29
- modified. Both use an ISO-style format value. The value of both can be
30
- determined by a call to the `ase_timestamp(format: "yyyy-LL-dd HH:mm")`
31
- tool of the `ase` MCP server, using the `text` field of its response.
32
- Whenever an **Artifact** is updated, the <timestamp-modified/> *MUST* be
33
- updated, too.
34
-
35
21
  The **Artifact Set** **Specification (SPEC)** consists of the following
36
22
  distinct **Artifact**s (listed under their <artifact-name/> and their
37
23
  <artifact-id/>):
@@ -6,7 +6,7 @@
6
6
  "homepage": "https://ase.tools",
7
7
  "repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
8
8
  "bugs": { "url": "https://github.com/rse/ase/issues" },
9
- "version": "0.9.56",
9
+ "version": "0.9.57",
10
10
  "license": "Apache-2.0",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ase-arch-analyze
3
- argument-hint: "[--help|-h] <source-reference>"
3
+ argument-hint: "[--help|-h] [--prefix|-P=<prefix>] <source-reference>"
4
4
  description: Review software architecture, including package cohesion and inter-package coupling
5
5
  user-invocable: true
6
6
  disable-model-invocation: false
@@ -9,7 +9,7 @@ disable-model-invocation: false
9
9
  effort: high
10
10
  allowed-tools:
11
11
  - "Bash(npm search --json *)"
12
- - "Bash(curl -s https://search.maven.org/*)"
12
+ - "Bash(curl -s *)"
13
13
  - "Skill"
14
14
  - "Agent"
15
15
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ase-code-resolve
3
- argument-hint: "[--help|-h] [--auto|-a] [--dry|-d] [--quick|-Q] [--next|-n <option>[,...]] [<task-id>:] <problem>"
3
+ argument-hint: "[--help|-h] [--auto|-a] [--dry|-d] [--direct|-D] [--quick|-Q] [--next|-n <option>[,...]] [<task-id>:] <problem>"
4
4
  description: >
5
5
  Resolve Problem:
6
6
  Use when user wants to "bugfix" or "fix" code or "resolve" a problem.
@@ -10,6 +10,7 @@ effort: xhigh
10
10
  allowed-tools:
11
11
  - "Skill"
12
12
  - "Agent"
13
+ - "Read"
13
14
  ---
14
15
 
15
16
  @${CLAUDE_SKILL_DIR}/../../meta/ase-control.md
@@ -23,7 +24,7 @@ Resolve Problem
23
24
 
24
25
  <expand name="getopt"
25
26
  arg1="ase-code-resolve"
26
- arg2="--auto|-a --dry|-d --quick|-Q --next|-n=(none|DONE|EDIT|GRILL|PREFLIGHT|IMPLEMENT)...">
27
+ arg2="--auto|-a --dry|-d --direct|-D --quick|-Q --next|-n=(none|DONE|EDIT|GRILL|PREFLIGHT|IMPLEMENT)...">
27
28
  $ARGUMENTS
28
29
  </expand>
29
30
 
@@ -45,9 +46,18 @@ to `true`, <getopt-option-dry/> to `true`, and <getopt-option-next/> to
45
46
  Procedure
46
47
  ---------
47
48
 
49
+ <if condition="<getopt-option-direct/> is not equal to 'true'">
48
50
  You *MUST* *NOT* call `Edit`, `Write`, `NotebookEdit`, or any
49
51
  filesystem-modifying tool during this entire skill. The *only*
50
52
  permitted way to persist artifacts is via `ase_task_save(...)`.
53
+ </if>
54
+ <else>
55
+ The `--direct`/`-D` mode applies the resolution *in place*, so STEP 4
56
+ below *requires* `Edit` and `Write` to modify the affected artifacts.
57
+ Every modification *MUST* still stay restricted to the artifacts the
58
+ resolution actually demands, and you *MUST* *NOT* call
59
+ `ase_task_save(...)`, as no task plan is composed at all.
60
+ </else>
51
61
 
52
62
  <flow>
53
63
 
@@ -120,14 +130,19 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
120
130
  ⧉ **ASE**: ⇌ problem: **<problem/>**
121
131
  </template>
122
132
 
123
- 7. Figure out what the requested <problem/> is about.
133
+ </step>
124
134
 
125
- 8. Ask the user for clarification if the goal of this resolution is
126
- too unclear.
135
+ 2. <step id="STEP 2: Investigate Code Base">
127
136
 
128
- 9. Do not output anything else in this step, unless you asked the user.
137
+ 1. Check the existing source files for all code which is related to the
138
+ requested <problem/> resolution.
129
139
 
130
- 10. Investigate and *figure out details* related to this problem.
140
+ 2. Check the architecture of the existing code base to understand the
141
+ overall structures and dynamics.
142
+
143
+ 3. Investigate and *figure out details* related to this problem.
144
+
145
+ <if condition="<getopt-option-direct/> is not equal to 'true'">
131
146
  Report those details with the following <template/>:
132
147
 
133
148
  <template>
@@ -165,38 +180,47 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
165
180
  run_in_background: false)`, reproducing its
166
181
  returned fenced code block verbatim. Omit <optional-diagram/>
167
182
  entirely for simple or purely local situations.
183
+ </if>
184
+
185
+ 4. Do not output anything else in this STEP 2.
168
186
 
169
187
  </step>
170
188
 
171
- 2. <step id="STEP 2: Investigate Code Base">
189
+ 3. <step id="STEP 3: Internalize Problem Resolution Tenets">
172
190
 
173
- 1. Check the existing source files for all code which is related to the
174
- requested <problem/> resolution.
191
+ 1. <task-kind>RESOLVING</task-kind>
175
192
 
176
- 2. Check the architecture of the existing code base to understand the
177
- overall structures and dynamics.
193
+ 2. <expand name="code-tenets" arg1="<task-kind/>"></expand>
178
194
 
179
- 3. Do not output anything in this STEP 2.
195
+ 3. Do not output anything in this STEP 3.
180
196
 
181
197
  </step>
182
198
 
183
- 3. <step id="STEP 3: Internalize Problem Resolution Tenets">
199
+ 4. <if condition="<getopt-option-direct/> is equal to 'true'">
184
200
 
185
- 1. <task-kind>RESOLVING</task-kind>
201
+ <step id="STEP 4: Direct Problem Resolution">
186
202
 
187
- 2. <expand name="code-tenets" arg1="<task-kind/>"></expand>
203
+ 1. Directly resolve the <problem/> by modifying the affected
204
+ *artifacts* with a corresponding, complete *change set*,
205
+ based on your gathered knowledge about the code base and your
206
+ internalized problem resolution tenets. Also, if a CHANGELOG.md
207
+ file exists, make an appropriate entry there, too.
188
208
 
189
- 3. Do not output anything in this STEP 3.
209
+ 2. Do not output anything else in this STEP 4. Especially, do not
210
+ output a change summary or a unified diff of the changes.
190
211
 
191
212
  </step>
192
213
 
193
- 4. <step id="STEP 4: Choose Problem Resolution Approaches">
214
+ </if>
215
+ <else>
216
+
217
+ <step id="STEP 4: Choose Problem Resolution Approaches">
194
218
 
195
219
  <expand name="code-approaches" arg1="resolution" arg2="resolution"></expand>
196
220
 
197
221
  </step>
198
222
 
199
- 5. <step id="STEP 5: Compose Problem Resolution Plan">
223
+ <step id="STEP 5: Compose Problem Resolution Plan">
200
224
 
201
225
  1. *Compose a plan* with code references, a precise description of the
202
226
  problem, the chosen resolution approach, a preview of the *unified
@@ -248,5 +272,7 @@ permitted way to persist artifacts is via `ase_task_save(...)`.
248
272
 
249
273
  </step>
250
274
 
275
+ </else>
276
+
251
277
  </flow>
252
278
 
@@ -9,6 +9,7 @@
9
9
  [`--help`|`-h`]
10
10
  [`--auto`|`-a`]
11
11
  [`--dry`|`-d`]
12
+ [`--direct`|`-D`]
12
13
  [`--quick`|`-Q`]
13
14
  [`--next`|`-n` *option*[,...]]
14
15
  [*task-id*:] *problem*
@@ -27,10 +28,11 @@ or `T1`) previously produced by `ase-code-analyze` or
27
28
  `ase-arch-analyze` and persisted in the `ase` MCP key/value store
28
29
  under `ase-issue-<id>`.
29
30
 
30
- The skill does *not* directly modify source files. It persists the
31
- plan via `ase_task_save` and then hands off to `ase-task-edit`,
32
- `ase-task-preflight`, or `ase-task-implement`, as selected by
33
- `--next`.
31
+ By default the skill does *not* directly modify source files. It
32
+ persists the plan via `ase_task_save` and then hands off to
33
+ `ase-task-edit`, `ase-task-preflight`, or `ase-task-implement`, as
34
+ selected by `--next`. Only under `--direct` it skips the plan
35
+ entirely and applies the change set to the affected artifacts itself.
34
36
 
35
37
  ## OPTIONS
36
38
 
@@ -45,6 +47,15 @@ plan via `ase_task_save` and then hands off to `ase-task-edit`,
45
47
  type-checker, or program execution) once the source files have
46
48
  been modified.
47
49
 
50
+ `--direct`|`-D`:
51
+ Resolve the problem *immediately* and *in place*: skip the
52
+ resolution approaches, the interactive dialog, and the entire task
53
+ plan ceremony, and directly apply the complete change set to the
54
+ affected artifacts, including a corresponding entry in an existing
55
+ `CHANGELOG.md` file. In this mode `--auto`, `--dry`, `--quick`, and
56
+ `--next` have no effect, as neither approaches are proposed nor a
57
+ plan is composed.
58
+
48
59
  `--quick`|`-Q`:
49
60
  Shorthand alias for `-a -d -n IMPLEMENT,DELETE`: automatically pick
50
61
  the recommended resolution approach, compose the plan *without* the
@@ -11,6 +11,7 @@ allowed-tools:
11
11
  - "Bash(git diff *)"
12
12
  - "Bash(git show *)"
13
13
  - "Bash(git tag --list *)"
14
+ - "Read"
14
15
  - "Write"
15
16
  - "Edit"
16
17
  ---
@@ -28,6 +28,15 @@ Query Foreign LLM for Chat
28
28
  Query foreign LLM for: <query><getopt-arguments/></query>
29
29
  </objective>
30
30
 
31
+ <if condition="<query/> is empty">
32
+ Only output the following <template/> and then immediately *STOP*
33
+ processing the entire current skill:
34
+
35
+ <template>
36
+ ⧉ **ASE**: ✪ skill: **ase-meta-chat**, ▶ ERROR: expected a `<query>` argument
37
+ </template>
38
+ </if>
39
+
31
40
  1. You *MUST* *NOT* output anything in this step.
32
41
  Just call the underlying agent with the following tool:
33
42
 
@@ -78,7 +78,8 @@ preset-bootstrapping operation that stays a shell concern next to
78
78
  `user`, `project`, `task:`*id*, and/or `session:`*id* terms, in any
79
79
  order and at most one term per kind. The `user` term is always
80
80
  implicitly added at the bottom of the chain, and `project` is
81
- implicitly added whenever a project context exists. If the option is
81
+ implicitly added whenever a project context exists, but never above
82
+ the strongest explicitly requested term. If the option is
82
83
  omitted, the chain of the *current session* (`session:`*id*) is used,
83
84
  so that reads see the full `user` -> `project` -> `session` cascade
84
85
  and writes -- including those to the session-only keys `agent.task`
@@ -143,32 +143,10 @@ Procedure
143
143
  </template>
144
144
 
145
145
  - If <result/> is `EDIT`:
146
- Set <args>--int-reuse-task</args>.
147
- <if condition="<getopt-option-next/> is not equal `none`">
148
- Set <args><args/> --next <getopt-option-next/></args>
149
- </if>
150
- Only output the following <template/> and then call the
151
- tool `Skill(skill: "ase:ase-task-edit", args: "<args/>")`
152
- to invoke the `ase:ase-task-edit` skill in order to *edit*
153
- the updated plan. Immediately stop processing the current
154
- skill once the `Skill` tool was used.
155
-
156
- <template>
157
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to edit**
158
- </template>
146
+ <expand name="task-next-handoff" arg1="ase-task-edit"
147
+ arg2="plan updated -- hand-off to edit"></expand>
159
148
 
160
149
  - If <result/> is `IMPLEMENT`:
161
- Set <args>--int-reuse-task</args>.
162
- <if condition="<getopt-option-next/> is not equal `none`">
163
- Set <args><args/> --next <getopt-option-next/></args>
164
- </if>
165
- Only output the following <template/> and then call the
166
- tool `Skill(skill: "ase:ase-task-implement", args: "<args/>")`
167
- to invoke the `ase:ase-task-implement` skill in order to
168
- *implement* the updated plan. Immediately stop processing
169
- the current skill once the `Skill` tool was used.
170
-
171
- <template>
172
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to implementation**
173
- </template>
150
+ <expand name="task-next-handoff" arg1="ase-task-implement"
151
+ arg2="plan updated -- hand-off to implementation"></expand>
174
152
 
@@ -143,47 +143,14 @@ Procedure
143
143
  </template>
144
144
 
145
145
  - If <result/> is `EDIT`:
146
- Set <args>--int-reuse-task</args>.
147
- <if condition="<getopt-option-next/> is not equal `none`">
148
- Set <args><args/> --next <getopt-option-next/></args>
149
- </if>
150
- Only output the following <template/> and then call the
151
- tool `Skill(skill: "ase:ase-task-edit", args: "<args/>")`
152
- to invoke the `ase:ase-task-edit` skill in order to *edit*
153
- the updated plan. Immediately stop processing the current
154
- skill once the `Skill` tool was used.
155
-
156
- <template>
157
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted -- hand-off to edit**
158
- </template>
146
+ <expand name="task-next-handoff" arg1="ase-task-edit"
147
+ arg2="plan rebooted -- hand-off to edit"></expand>
159
148
 
160
149
  - If <result/> is `IMPLEMENT`:
161
- Set <args>--int-reuse-task</args>.
162
- <if condition="<getopt-option-next/> is not equal `none`">
163
- Set <args><args/> --next <getopt-option-next/></args>
164
- </if>
165
- Only output the following <template/> and then call the
166
- tool `Skill(skill: "ase:ase-task-implement", args: "<args/>")`
167
- to invoke the `ase:ase-task-implement` skill in order to
168
- *implement* the updated plan. Immediately stop processing
169
- the current skill once the `Skill` tool was used.
170
-
171
- <template>
172
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted -- hand-off to implementation**
173
- </template>
150
+ <expand name="task-next-handoff" arg1="ase-task-implement"
151
+ arg2="plan rebooted -- hand-off to implementation"></expand>
174
152
 
175
153
  - If <result/> is `PREFLIGHT`:
176
- Set <args>--int-reuse-task</args>.
177
- <if condition="<getopt-option-next/> is not equal `none`">
178
- Set <args><args/> --next <getopt-option-next/></args>
179
- </if>
180
- Only output the following <template/> and then call the
181
- tool `Skill(skill: "ase:ase-task-preflight", args: "<args/>")`
182
- to invoke the `ase:ase-task-preflight` skill in order to
183
- *preflight* the updated plan. Immediately stop processing
184
- the current skill once the `Skill` tool was used.
185
-
186
- <template>
187
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted -- hand-off to pre-flight**
188
- </template>
154
+ <expand name="task-next-handoff" arg1="ase-task-preflight"
155
+ arg2="plan rebooted -- hand-off to pre-flight"></expand>
189
156