@rse/ase 0.9.13 → 0.9.15

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-kv.js CHANGED
@@ -45,10 +45,21 @@ export class KV {
45
45
  KV.validateKey(key);
46
46
  return KV.store.delete(key);
47
47
  }
48
- /* clear all keys; returns the number of keys removed */
49
- static clear() {
50
- const n = KV.store.size;
51
- KV.store.clear();
48
+ /* clear all keys, or just those starting with the given `prefix`;
49
+ returns the number of keys removed */
50
+ static clear(prefix) {
51
+ if (prefix === undefined || prefix === "") {
52
+ const n = KV.store.size;
53
+ KV.store.clear();
54
+ return n;
55
+ }
56
+ let n = 0;
57
+ for (const key of KV.store.keys()) {
58
+ if (key.startsWith(prefix)) {
59
+ KV.store.delete(key);
60
+ n++;
61
+ }
62
+ }
52
63
  return n;
53
64
  }
54
65
  /* snapshot the entire store into a fresh map (for transactional batch) */
@@ -112,12 +123,17 @@ export class KVMCP {
112
123
  /* key/value clear */
113
124
  mcp.registerTool("ase_kv_clear", {
114
125
  title: "ASE key/value clear",
115
- description: "Remove all keys from the in-memory key/value store. " +
126
+ description: "Remove keys from the in-memory key/value store. " +
127
+ "If `prefix` is given, only keys starting with `prefix` are removed; " +
128
+ "otherwise all keys are removed. " +
116
129
  "Returns a status `text` indicating how many keys were removed.",
117
- inputSchema: {}
118
- }, async () => {
130
+ inputSchema: {
131
+ prefix: z.string().optional()
132
+ .describe("if given, only remove keys starting with this prefix (otherwise remove all keys)")
133
+ }
134
+ }, async (args) => {
119
135
  try {
120
- const n = KV.clear();
136
+ const n = KV.clear(args.prefix);
121
137
  return { content: [{ type: "text", text: `kv_clear: OK: removed ${n} key(s)` }] };
122
138
  }
123
139
  catch (err) {
@@ -151,8 +167,9 @@ export class KVMCP {
151
167
  mcp.registerTool("ase_kv_batch", {
152
168
  title: "ASE key/value batch",
153
169
  description: "Execute an array of in-memory key/value `commands` in a single MCP call. " +
154
- "Each entry is an object `{ command: \"clear\"|\"set\"|\"get\"|\"delete\", key?, val? }` " +
170
+ "Each entry is an object `{ command: \"clear\"|\"set\"|\"get\"|\"delete\", key?, val?, prefix? }` " +
155
171
  "and is dispatched to the corresponding single-op tool. " +
172
+ "For `clear`, an optional `prefix` restricts removal to keys starting with it. " +
156
173
  "If `transactional` is true, the store is snapshotted up-front and rolled back on the " +
157
174
  "first per-command error (remaining commands are skipped); otherwise per-command errors " +
158
175
  "are recorded and execution continues. " +
@@ -168,7 +185,9 @@ export class KVMCP {
168
185
  key: z.string().optional()
169
186
  .describe("key identifier (required for `set`/`get`/`delete`)"),
170
187
  val: z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.any()), z.record(z.string(), z.any())]).optional()
171
- .describe("value to store (required for `set`)")
188
+ .describe("value to store (required for `set`)"),
189
+ prefix: z.string().optional()
190
+ .describe("if given for `clear`, only remove keys starting with this prefix (otherwise remove all keys)")
172
191
  }))
173
192
  .describe("ordered list of KV commands to execute"),
174
193
  transactional: z.boolean().optional()
@@ -182,7 +201,7 @@ export class KVMCP {
182
201
  for (const c of args.commands) {
183
202
  try {
184
203
  if (c.command === "clear") {
185
- const n = KV.clear();
204
+ const n = KV.clear(c.prefix);
186
205
  results.push(`kv_clear: OK: removed ${n} key(s)`);
187
206
  cmdNames.push("clear");
188
207
  }
package/dst/ase-mcp.js CHANGED
@@ -56,8 +56,13 @@ export default class MCPCommand {
56
56
  }
57
57
  /* bridge stdio to a Streamable HTTP MCP endpoint on the local service */
58
58
  async runBridge() {
59
- /* ensure the service is running */
60
- let { projectId, port } = await this.ensureService();
59
+ /* tentatively load the service identity context; the actual service
60
+ start/probe happens below through ensureService() and, on failure,
61
+ is funnelled into the same reconnect machinery used at runtime so
62
+ that a transient startup failure is recoverable instead of fatal */
63
+ const ctx0 = this.loadContext();
64
+ let projectId = ctx0.projectId;
65
+ let port = ctx0.port ?? 0;
61
66
  /* create MCP STDIO server (lives for the entire bridge lifetime) */
62
67
  const server = new StdioServerTransport();
63
68
  /* track active client and bridge-level closed state */
@@ -154,10 +159,17 @@ export default class MCPCommand {
154
159
  /* start server and initial client */
155
160
  await server.start();
156
161
  try {
162
+ /* ensure the service is running, then connect; both the
163
+ service start/probe and the connect are covered here so that
164
+ an initial failure recovers via the reconnect machinery
165
+ (now in place) instead of crashing the bridge outright */
166
+ const ctx = await this.ensureService();
167
+ projectId = ctx.projectId;
168
+ port = ctx.port;
157
169
  await connectClient();
158
170
  }
159
171
  catch (err) {
160
- /* service vanished between probe and connect — recover instead of crashing */
172
+ /* initial service start or connect failed — recover instead of crashing */
161
173
  triggerReconnect(`initial connect failed: ${this.asError(err).message}`);
162
174
  }
163
175
  /* periodically probe the service; trigger reconnect if it is gone */
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.13",
9
+ "version": "0.9.15",
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.9.13",
3
+ "version": "0.9.15",
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.13",
3
+ "version": "0.9.15",
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.13",
3
+ "version": "0.9.15",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -13,9 +13,9 @@ tools:
13
13
 
14
14
  @../meta/ase-control.md
15
15
 
16
- 1. Treat `$ARGUMENTS` as a single whitespace-separated string.
17
- Set <llm/> to the *first* token.
18
- Set <query/> to the *second and all following* tokens.
16
+ 1. Set <args>$ARGUMENTS</args>, the single whitespace-separated string.
17
+ Set <llm/> to the *first* token of <args/>.
18
+ Set <query/> to the *second and all following* tokens of <args/>.
19
19
  You *MUST* *NOT* output anything related to this step.
20
20
 
21
21
  2. Set <server></server> (set to empty).
@@ -96,7 +96,8 @@ set placeholders into the context as a side-effect.
96
96
  `<getopt-result/>.opts[<longN/>]`.
97
97
  Set <getopt-arguments/> to the value of `<getopt-result/>.args`.
98
98
  Set <getopt-info/> to `<getopt-result/>.info`, but remove
99
- information about the `help` option.
99
+ information about the `help` option and any *internal* option
100
+ whose long name starts with `int-`.
100
101
 
101
102
  7. **Display Results**:
102
103
  Just output the following <template/>:
@@ -250,8 +250,9 @@ Template Patterns
250
250
  - When `<ase-tpl-head title="<title/>"/>` should be expanded, use
251
251
  (where <raw-title/> is the visible un-styled text `⧉ ASE: <title/>`,
252
252
  <raw-title-len/> is the number of characters in <raw-title/>, and
253
- <bar/> is the `─` character repeated exactly (67 - <raw-title-len/>)
254
- times -- the very same bar-width rule as `<ase-tpl-foot/>` and
253
+ <bar/> is the `─` character repeated exactly max(0, 67 - <raw-title-len/>)
254
+ times -- clamped to zero so an over-long title never yields a negative
255
+ count -- the very same bar-width rule as `<ase-tpl-foot/>` and
255
256
  `<ase-tpl-boxed/>`, so equal visible text yields equal total width):
256
257
 
257
258
  <template>
@@ -271,8 +272,9 @@ Template Patterns
271
272
  - When `<ase-tpl-foot title="<title/>"/>` should be expanded, use
272
273
  (where <raw-title/> is the visible un-styled text `⧉ ASE: <title/>`,
273
274
  <raw-title-len/> is the number of characters in <raw-title/>, and
274
- <bar/> is the `─` character repeated exactly (67 - <raw-title-len/>)
275
- times -- the very same bar-width rule as `<ase-tpl-head/>` and
275
+ <bar/> is the `─` character repeated exactly max(0, 67 - <raw-title-len/>)
276
+ times -- clamped to zero so an over-long title never yields a negative
277
+ count -- the very same bar-width rule as `<ase-tpl-head/>` and
276
278
  `<ase-tpl-boxed/>`, so equal visible text yields equal total width):
277
279
 
278
280
  <template>
@@ -308,8 +310,9 @@ Template Patterns
308
310
  </else>
309
311
  - Set <raw-title-len/> to the number of characters in the visible
310
312
  un-styled text <raw-title/>.
311
- - Set <bar/> to the `─` character repeated exactly (67 - <raw-title-len/>)
312
- times -- the very same bar-width rule as `<ase-tpl-head/>` and
313
+ - Set <bar/> to the `─` character repeated exactly max(0, 67 - <raw-title-len/>)
314
+ times -- clamped to zero so an over-long title never yields a negative
315
+ count -- the very same bar-width rule as `<ase-tpl-head/>` and
313
316
  `<ase-tpl-foot/>`, so equal visible text yields equal total width.
314
317
  - Set <body/> to <content/> with all line-starts prefixed with `│ `.
315
318
 
@@ -348,7 +351,8 @@ Template Patterns
348
351
  <template>🟠</template>
349
352
 
350
353
  - When `<ase-tpl-pad width="<width/>" text="<text/>"/>` should be expanded, use
351
- (where <ws/> = ` ` x (<width/> - length("<text/>")), i.e., <ws/> is
352
- the ` ` character repeated (<width/> - length("<text/>")) times):
354
+ (where <ws/> = ` ` x max(0, <width/> - length("<text/>")), i.e., <ws/> is
355
+ the ` ` character repeated max(0, <width/> - length("<text/>")) times --
356
+ clamped to zero so an over-long text never yields a negative count):
353
357
 
354
358
  <template><text/><ws/></template>
@@ -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.13",
9
+ "version": "0.9.15",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -399,9 +399,13 @@ interface quality, quality attributes, and architecture governance.
399
399
  DEPENDENCY-DIRECTION`).
400
400
 
401
401
  - The <focal-aspect/> is the aspect that participates in
402
- *all* tensions of the cluster. For a size-2 cluster, pick
403
- the aspect whose direction is most constrained by the
404
- detected style.
402
+ *all* tensions of the cluster. In a size-2 cluster both
403
+ aspects participate equally in the single tension, so this
404
+ rule cannot disambiguate; instead pick the focal aspect by
405
+ the first applicable tiebreaker: (1) the aspect whose
406
+ direction is more constrained by the detected style; else
407
+ (2) the aspect carrying the *higher* finding severity; else
408
+ (3) the aspect listed *first* in the tension matrix pair.
405
409
 
406
410
  - *Brevity and precision*: all free-form placeholders
407
411
  (<description/>, <focal-state/>, partner-implications)
@@ -438,9 +442,11 @@ interface quality, quality attributes, and architecture governance.
438
442
  - *Additionally*, persist all reported findings in a *single*
439
443
  `ase_kv_batch` call to the `ase` MCP server with `transactional`
440
444
  set to `true`. The `commands` parameter array of this call
441
- starts with one `{ command: "clear" }` entry, followed by one
442
- `{ command: "set", key: "ase-issue-P<n/>", val: "<title/>:
443
- <description/>" }` entry per reported PROBLEM and one
445
+ starts with one `{ command: "clear", prefix: "ase-issue-" }`
446
+ entry (which removes only the previously persisted `ase-issue-*`
447
+ keys, leaving any unrelated keys in the shared store intact),
448
+ followed by one `{ command: "set", key: "ase-issue-P<n/>", val:
449
+ "<title/>: <description/>" }` entry per reported PROBLEM and one
444
450
  `{ command: "set", key: "ase-issue-T<n/>", val: "<title/>:
445
451
  <description/>" }` entry per reported TRADEOFF.
446
452
  </step>
@@ -115,8 +115,16 @@ for the technology stack to *provide* the *needed functionality*
115
115
  <keyword-L/> (L=1-M), which allow you to search for suitable
116
116
  components.
117
117
 
118
- 3. In the to be discovered result set of components <component-K/>
119
- (K=1-N), remember the component name as <name-K/>, the
118
+ 3. Determine the *candidate pool size* <pool/> as twice the
119
+ <getopt-option-limit/> (i.e. <pool/> = 2 &times;
120
+ <getopt-option-limit/>). Each discovery source below may fetch up
121
+ to <pool/> candidates so that the later ranking and trimming
122
+ (which alone is governed by <getopt-option-limit/>) has a
123
+ meaningful set to choose from.
124
+
125
+ In the to be discovered candidate set of components <component-K/>
126
+ (K=1-C, where C is the merged and deduplicated candidate count),
127
+ remember the component name as <name-K/>, the
120
128
  official package name as <package-K/>, the latest version as
121
129
  <version-K/>, the stars as <stars-K/>, the created date as
122
130
  <created-K/>, the last updated date as <updated-K/>, the total
@@ -126,13 +134,13 @@ for the technology stack to *provide* the *needed functionality*
126
134
 
127
135
  1. Based on the essential keywords <keyword-L/> (L=1-M),
128
136
  use the `ase-meta-search` skill in a subagent to *generally*
129
- discover an initial set of a maximum of <getopt-option-limit/> *NPM packages*
137
+ discover an initial set of a maximum of <pool/> *NPM packages*
130
138
  <component-K/> and at least their real name <name-K/> and
131
139
  their unique package names <package-K/>.
132
140
 
133
- 2. Use the shell command `npm search --json --searchlimit <getopt-option-limit/>
141
+ 2. Use the shell command `npm search --json --searchlimit <pool/>
134
142
  "<keyword-1/>" [...] "<keyword-M/>"` to *specifically*
135
- discover an additional set of a maximum of <getopt-option-limit/> *NPM packages*
143
+ discover an additional set of a maximum of <pool/> *NPM packages*
136
144
  <component-K/> and at least their unique package names
137
145
  <package-K/>, based on the essential keywords <keyword-L/>
138
146
  (L=1-M). Merge the results into the already existing result
@@ -142,14 +150,14 @@ for the technology stack to *provide* the *needed functionality*
142
150
 
143
151
  1. Based on the essential keywords <keyword-L/> (L=1-M),
144
152
  use the `ase-meta-search` skill in a subagent to *generally*
145
- discover an initial set of a maximum of <getopt-option-limit/> *Maven packages*
153
+ discover an initial set of a maximum of <pool/> *Maven packages*
146
154
  <component-K/> and at least their real name <name-K/> and
147
155
  their unique Maven coordinates <package-K/> of the form
148
156
  `groupId:artifactId`.
149
157
 
150
- 2. Use the shell command `curl -s 'https://search.maven.org/solrsearch/select?q=<keyword-1/>+[...]+<keyword-M/>&rows=<getopt-option-limit/>&wt=json'`
158
+ 2. Use the shell command `curl -s 'https://search.maven.org/solrsearch/select?q=<keyword-1/>+[...]+<keyword-M/>&rows=<pool/>&wt=json'`
151
159
  to *specifically* discover an additional set of a maximum
152
- of <getopt-option-limit/> *Maven packages* <component-K/> and at least their
160
+ of <pool/> *Maven packages* <component-K/> and at least their
153
161
  unique Maven coordinates <package-K/> (i.e. `<g/>:<a/>` from
154
162
  each result document's `g` and `a` fields), based on the
155
163
  essential keywords <keyword-L/> (L=1-M). Merge the results
@@ -157,22 +165,25 @@ for the technology stack to *provide* the *needed functionality*
157
165
  entries by Maven coordinate.
158
166
 
159
167
  6. Call the `ase_component_info(stack: "<stack/>", components:
160
- [ "<package-1/>", ..., "<package-N/>" ])` tool of the `ase` MCP
161
- server *once* for the entire set of discovered packages.
168
+ [ "<package-1/>", ..., "<package-C/>" ])` tool of the `ase` MCP
169
+ server *once* for the entire candidate set of discovered packages.
162
170
  The tool dispatches internally on <stack/> and fetches all
163
171
  metadata in maximum parallel and returns an array of objects `{
164
172
  name, version, created, updated, repository, stars, downloads,
165
173
  rank }`. For each
166
- component <component-K/> (K=1-N) read from its corresponding
174
+ component <component-K/> (K=1-C) read from its corresponding
167
175
  entry: <version-K/> from `version`, <updated-K/> from `updated`,
168
176
  <created-K/> from `created`, <repository-K/> from `repository`,
169
177
  <stars-K/> from `stars` (numeric or `N.A.`), <downloads-K/>
170
178
  from `downloads` (numeric or `N.A.`) and <rank-K/> from `rank`
171
179
  (numeric).
172
180
 
173
- 7. Sort, in descending order, the discovered components
174
- <component-K/> (K=1-N) by their `rank` field and trim the result
175
- list to just a maximum of <getopt-option-limit/> total components.
181
+ 7. Sort, in descending order, the discovered candidate components
182
+ <component-K/> (K=1-C) by their `rank` field and trim the result
183
+ list to just a maximum of <getopt-option-limit/> total components,
184
+ which establishes the final retained set <component-K/> (K=1-N,
185
+ where N is at most <getopt-option-limit/>). All subsequent steps
186
+ operate solely on this retained set.
176
187
 
177
188
  8. For each component <component-K/> (K=1-N), research and then
178
189
  decide which *one* of *USP* (Unique Selling Point -- what makes
@@ -89,6 +89,27 @@ problems in *performance* and *efficiency*, or problems in *security*.
89
89
  - many I/O and query round-trips
90
90
  - concurrency bottlenecks
91
91
  - mismatched data structures
92
+ - N+1 query patterns (1 parent query, N child queries)
93
+ - missing caching/memoization of stable results
94
+ - blocking/synchronous calls on hot paths
95
+ - unbounded growth (memory leaks, ever-growing collections)
96
+ - inefficient string building/concatenation in loops
97
+ - premature or repeated serialization/parsing
98
+ - lack of batching/pagination for bulk operations
99
+ - excessive logging or instrumentation overhead
100
+ - chatty network protocols (no connection pooling/keep-alive)
101
+ - lock contention and overly coarse-grained locking
102
+ - eager/over-fetching of data that is never used
103
+ - missing database indexes
104
+ - repeated regex (re)compilation in hot paths
105
+ - busy-waiting/polling instead of event-driven waits
106
+ - transferring uncompressed or overly verbose payloads
107
+ - missing short-circuit evaluation in expensive conditions
108
+ - recomputing invariants inside loops (loop-invariant code)
109
+ - suboptimal batch sizes (too small = overhead, too large = latency)
110
+ - inefficient algorithms for sorting/searching already-ordered data
111
+ - redundant validation/sanitization of trusted internal data
112
+ - missing connection/resource reuse (open-close per operation)
92
113
  - [...]
93
114
 
94
115
  </if>
@@ -107,6 +128,35 @@ problems in *performance* and *efficiency*, or problems in *security*.
107
128
  - unsafe cryptography
108
129
  - hard-coded secrets
109
130
  - vulnerable dependencies
131
+ - injection flaws
132
+ - cross-site scripting (XSS) and output-encoding gaps
133
+ - cross-site request forgery (CSRF) and missing anti-forgery tokens
134
+ - insecure direct object references (IDOR)
135
+ - server-side request forgery (SSRF)
136
+ - insecure or missing transport encryption (TLS)
137
+ - weak session management (fixation, predictable tokens)
138
+ - missing rate limiting/anti-automation controls
139
+ - overly permissive CORS or file permissions
140
+ - verbose error messages leaking internals
141
+ - unsafe randomness for security-sensitive values
142
+ - mass assignment / over-binding of request parameters
143
+ - security misconfiguration (default credentials, debug modes, exposed admin endpoints)
144
+ - missing or misconfigured security headers (CSP, HSTS, X-Frame-Options)
145
+ - improper certificate/hostname validation (TLS verification disabled)
146
+ - insufficient logging and monitoring of security events
147
+ - race conditions / TOCTOU (time-of-check to time-of-use) flaws
148
+ - integer overflow/underflow and buffer overflows
149
+ - use-after-free and memory-safety violations
150
+ - privilege escalation through improper privilege dropping
151
+ - insecure file upload handling (unrestricted type/size, executable storage)
152
+ - unsafe handling of untrusted regular expressions (ReDoS)
153
+ - caching of sensitive data in shared or client-side caches
154
+ - secrets or sensitive data leaking into logs, traces, or telemetry
155
+ - insecure default-deny failures (fail-open instead of fail-closed)
156
+ - missing integrity verification (unsigned updates, no subresource integrity)
157
+ - excessive data exposure in API responses (returning more fields than needed)
158
+ - improper resource cleanup leading to exhaustion (connection/file-descriptor leaks)
159
+ - business-logic flaws (bypassable workflows, negative quantities, replay)
110
160
  - [...]
111
161
 
112
162
  </if>
@@ -130,6 +180,48 @@ problems in *performance* and *efficiency*, or problems in *security*.
130
180
  - null/undefined mishandling
131
181
  - type-coercion bugs
132
182
  - faulty parsing or merge/override semantics
183
+ - race conditions and unsynchronized shared state
184
+ - resource leaks (unclosed files, handles, connections)
185
+ - inverted or swapped function arguments
186
+ - incorrect loop termination or accumulator initialization
187
+ - shadowed or reassigned variables changing intent
188
+ - incomplete switch/case or enum coverage
189
+ - silent exception swallowing
190
+ - floating-point comparison and rounding errors
191
+ - integer overflow/underflow or truncating division
192
+ - sign and modulo errors with negative operands
193
+ - reference vs. value semantics (aliasing, shared mutable defaults)
194
+ - incorrect short-circuit evaluation or operator precedence
195
+ - logical vs. bitwise operator confusion
196
+ - negation mistakes in compound predicates
197
+ - wrong comparison operator (`==` instead of `===`, `<` vs. `<=`, etc)
198
+ - inverted condition or swapped if/else branches
199
+ - dead or duplicated conditional branches
200
+ - fall-through where a break or return was intended
201
+ - missing or misplaced base case in recursion (non-termination)
202
+ - mutation of a collection while iterating over it
203
+ - incorrect index, key, or bounds when accessing collections
204
+ - empty-collection or single-element edge cases unhandled
205
+ - wrong order of operations in initialization or teardown
206
+ - missing cleanup on early return, break, or exception path
207
+ - double-free, use-after-free, or double-close of resources
208
+ - incorrect time-zone, date arithmetic, or unit conversions
209
+ - stale cache or memoization not invalidated on change
210
+ - incorrect deep vs. shallow copy semantics
211
+ - partial or non-atomic updates leaving inconsistent state
212
+ - ignored or unchecked return values and status codes
213
+ - error code vs. exception path mismatch
214
+ - catching too broad an exception masking real failures
215
+ - re-throwing without preserving the original cause
216
+ - incorrect equality, hashing, or ordering for custom types
217
+ - regex anchoring, greediness, or escaping mistakes
218
+ - string encoding, normalization, or case-folding errors
219
+ - missing `await` causing unhandled or dropped promises
220
+ - incorrect promise concurrency (`all` vs. `allSettled`, races)
221
+ - callback invoked zero times, twice, or out of order
222
+ - unguarded re-entrancy or recursive lock acquisition
223
+ - incorrect guard ordering allowing invalid states through
224
+ - assumptions about iteration order of maps/sets/objects
133
225
  - [...]
134
226
 
135
227
  </if>
@@ -151,8 +243,12 @@ problems in *performance* and *efficiency*, or problems in *security*.
151
243
 
152
244
  Then renumber the surviving problems contiguously as `P<n/>` with
153
245
  <n/> = 1, 2, ... in the original ordering. If *all* problems are
154
- dropped, skip the per-problem report but still emit the final hint
155
- <template/> below.
246
+ dropped, skip the per-problem report but still purge any stale
247
+ persisted problems with a *single* `ase_kv_batch` call to the `ase`
248
+ MCP server with `transactional` set to `true` and a `commands`
249
+ parameter array holding exactly one `{ command: "clear", prefix:
250
+ "ase-issue-" }` entry,
251
+ and still emit the final hint <template/> below.
156
252
 
157
253
  In this STEP 3, for *EVERY* surviving problem, immediately report
158
254
  it with the following output <template/>, based on concise bullet
@@ -234,9 +330,11 @@ problems in *performance* and *efficiency*, or problems in *security*.
234
330
  - *Additionally*, persist all reported problems in a *single*
235
331
  `ase_kv_batch` call to the `ase` MCP server with `transactional`
236
332
  set to `true`. The `commands` parameter array of this call
237
- starts with one `{ command: "clear" }` entry, followed by one
238
- `{ command: "set", key: "ase-issue-P<n/>", val: "<title/>:
239
- <description/>" }` entry per reported problem.
333
+ starts with one `{ command: "clear", prefix: "ase-issue-" }`
334
+ entry (which removes only the previously persisted `ase-issue-*`
335
+ keys, leaving any unrelated keys in the shared store intact),
336
+ followed by one `{ command: "set", key: "ase-issue-P<n/>", val:
337
+ "<title/>: <description/>" }` entry per reported problem.
240
338
 
241
339
  Finally, output the following <template/> to give a final hint:
242
340
 
@@ -107,6 +107,16 @@ related to a set of code quality aspects.
107
107
  "ase-code-lint", scope: "session:<ase-session-id/>")` tool
108
108
  from the `ase` MCP server. Do not output anything in this substep.
109
109
 
110
+ *Critical safety invariant*: the marker set here grants `Edit`
111
+ auto-approval and *MUST* be cleared again (substep 3 below)
112
+ *before* this skill yields control, *regardless* of how the
113
+ iteration in substep 2 ends - whether it completes normally,
114
+ is aborted early (e.g. an `Edit` failure, an unparseable value,
115
+ or any other unexpected condition), or is otherwise interrupted.
116
+ If you ever stop or bail out of substep 2 early, you *MUST*
117
+ still perform substep 3 first. Never leave this marker active
118
+ for a later, unrelated `Edit`.
119
+
110
120
  2. Iterate over all problems:
111
121
 
112
122
  <for items="<problems/>">
@@ -143,16 +153,46 @@ related to a set of code quality aspects.
143
153
  Set <new-text/> to the `new_text` field of <item/>.
144
154
  Set <context-after/> to the `context_after` field of <item/>.
145
155
 
146
- 2. Set <old-start/> to the line of the first hunk line, i.e.,
147
- the line of <context-before/> (one before <line/>).
148
- Set <new-start/> to the same as <old-start/> (the unchanged
149
- <context-before/> line shares the same start in both files).
150
- Set <old-count/> to the total number of old-side hunk lines, i.e.,
151
- the number of lines in <context-before/>, <old-text/>, and
152
- <context-after/> combined.
153
- Set <new-count/> to the total number of new-side hunk lines, i.e.,
154
- the number of lines in <context-before/>, <new-text/>, and
155
- <context-after/> combined.
156
+ 2. Determine the hunk *body* as an ordered list of lines,
157
+ each carrying a one-character prefix (` ` for context,
158
+ `-` for old-side, `+` for new-side). Build it by
159
+ concatenating, in order and *skipping any part that is
160
+ empty*:
161
+
162
+ - one ` `-prefixed line for *each* line of
163
+ <context-before/> (if non-empty),
164
+ - one `-`-prefixed line for *each* line of <old-text/>
165
+ (if non-empty; split <old-text/> on newlines),
166
+ - one `+`-prefixed line for *each* line of <new-text/>
167
+ (if non-empty; split <new-text/> on newlines),
168
+ - one ` `-prefixed line for *each* line of
169
+ <context-after/> (if non-empty).
170
+
171
+ Set <hunk-body/> to those prefixed lines joined by
172
+ newlines.
173
+
174
+ Set <old-count/> to the number of old-side hunk lines,
175
+ i.e., the combined line count of <context-before/>,
176
+ <old-text/>, and <context-after/> (each empty part
177
+ counts as `0`).
178
+ Set <new-count/> to the number of new-side hunk lines,
179
+ i.e., the combined line count of <context-before/>,
180
+ <new-text/>, and <context-after/> (each empty part
181
+ counts as `0`).
182
+
183
+ Set <old-start/> to the 1-based line number of the
184
+ *first* old-side hunk line: if <context-before/> is
185
+ non-empty, that is its line (one before <line/>);
186
+ otherwise it is <line/> itself (the first line of
187
+ <old-text/>). For a hunk that *only inserts* new lines
188
+ (empty <old-text/> *and* empty context), set it to the
189
+ line *before* which the insertion happens, clamped to a
190
+ minimum of `0`, so a top-of-file insertion yields
191
+ `@@ -0,0 ... @@`.
192
+ Set <new-start/> to the same value as <old-start/>, but
193
+ clamped to a minimum of `1` whenever <new-count/> is
194
+ greater than `0` (the corrected side then has a real
195
+ first line).
156
196
 
157
197
  3. If <context/> is not empty, set
158
198
  <context><context/>,</context> (append a comma).
@@ -160,14 +200,13 @@ related to a set of code quality aspects.
160
200
 
161
201
  <template>`<file/>`:<line/></template>
162
202
 
163
- 4. Append the following <template/> to <diff-file/>:
203
+ 4. Append the following <template/> to <diff-file/>,
204
+ emitting <hunk-body/> verbatim (one already-prefixed
205
+ line per line, with no extra blank or space-only lines):
164
206
 
165
207
  <template>
166
208
  @@ -<old-start/>,<old-count/> +<new-start/>,<new-count/> @@
167
- <context-before/>
168
- -<old-text/>
169
- +<new-text/>
170
- <context-after/>
209
+ <hunk-body/>
171
210
  </template>
172
211
 
173
212
  </for>
@@ -96,6 +96,16 @@ Analyze documents for spelling, punctuation, or grammar errors
96
96
  "ase-docs-proofread", scope: "session:<ase-session-id/>")` tool
97
97
  from the `ase` MCP server. Do not output anything in this substep.
98
98
 
99
+ *Critical safety invariant*: the marker set here grants `Edit`
100
+ auto-approval and *MUST* be cleared again (substep 3 below)
101
+ *before* this skill yields control, *regardless* of how the
102
+ iteration in substep 2 ends - whether it completes normally,
103
+ is aborted early (e.g. an `Edit` failure, an unparseable value,
104
+ or any other unexpected condition), or is otherwise interrupted.
105
+ If you ever stop or bail out of substep 2 early, you *MUST*
106
+ still perform substep 3 first. Never leave this marker active
107
+ for a later, unrelated `Edit`.
108
+
99
109
  2. Iterate over all problems:
100
110
 
101
111
  <for items="<problems/>">
@@ -119,19 +129,44 @@ Analyze documents for spelling, punctuation, or grammar errors
119
129
 
120
130
  3. <if condition="<getopt-option-auto/> is not 'true'">
121
131
 
122
- Set <old-start/> to the line of the first hunk line, i.e.,
123
- the line of <context-before/> (one before <line/>).
124
- Set <new-start/> to the same as <old-start/> (the unchanged
125
- <context-before/> line shares the same start in both files).
126
- Set <old-count/> to the total number of old-side hunk lines, i.e.,
127
- the number of lines in <context-before/>, <old-text/>, and
128
- <context-after/> combined.
129
- Set <new-count/> to the total number of new-side hunk lines, i.e.,
130
- the number of lines in <context-before/>, <new-text/>, and
131
- <context-after/> combined.
132
+ Determine the hunk *body* as an ordered list of lines, each
133
+ carrying a one-character prefix (` ` for context, `-` for
134
+ old-side, `+` for new-side). Build it by concatenating, in
135
+ order and *skipping any part that is empty*:
136
+
137
+ - one ` `-prefixed line for *each* line of <context-before/>
138
+ (if non-empty),
139
+ - one `-`-prefixed line for *each* line of <old-text/>
140
+ (if non-empty; split <old-text/> on newlines),
141
+ - one `+`-prefixed line for *each* line of <new-text/>
142
+ (if non-empty; split <new-text/> on newlines),
143
+ - one ` `-prefixed line for *each* line of <context-after/>
144
+ (if non-empty).
145
+
146
+ Set <hunk-body/> to those prefixed lines joined by newlines.
147
+
148
+ Set <old-count/> to the number of old-side hunk lines, i.e.,
149
+ the combined line count of <context-before/>, <old-text/>, and
150
+ <context-after/> (each empty part counts as `0`).
151
+ Set <new-count/> to the number of new-side hunk lines, i.e.,
152
+ the combined line count of <context-before/>, <new-text/>, and
153
+ <context-after/> (each empty part counts as `0`).
154
+
155
+ Set <old-start/> to the 1-based line number of the *first*
156
+ old-side hunk line: if <context-before/> is non-empty, that is
157
+ its line (one before <line/>); otherwise it is <line/> itself
158
+ (the first line of <old-text/>). For a hunk that *only inserts*
159
+ new lines (empty <old-text/> *and* empty context), set it to the
160
+ line *before* which the insertion happens, clamped to a minimum
161
+ of `0`, so a top-of-file insertion yields `@@ -0,0 ... @@`.
162
+ Set <new-start/> to the same value as <old-start/>, but clamped
163
+ to a minimum of `1` whenever <new-count/> is greater than `0`
164
+ (the corrected side then has a real first line).
132
165
 
133
166
  Render the proposed correction as a *unified diff* with *up to
134
- two* lines of context in a fenced block based on the following <template/>:
167
+ two* lines of context in a fenced block based on the following
168
+ <template/>, emitting <hunk-body/> verbatim (one already-prefixed
169
+ line per line, with no extra blank or space-only lines):
135
170
 
136
171
  <template>
137
172
 
@@ -141,10 +176,7 @@ Analyze documents for spelling, punctuation, or grammar errors
141
176
  --- <file/> (original)
142
177
  +++ <file/> (corrected)
143
178
  @@ -<old-start/>,<old-count/> +<new-start/>,<new-count/> @@
144
- <context-before/>
145
- -<old-text/>
146
- +<new-text/>
147
- <context-after/>
179
+ <hunk-body/>
148
180
  ```
149
181
 
150
182
  </template>
@@ -137,8 +137,11 @@ Honor the following tenets throughout the brainstorming:
137
137
  4. Dispatch on the dialog <result/>:
138
138
 
139
139
  - If <result/> is `CANCEL`:
140
- Skip the remaining sub-steps of this iteration cycle and
141
- continue directly with sub-step 2 below (cancellation handling).
140
+ Skip the remaining sub-steps of this iteration cycle, break
141
+ out of the iteration cycle entirely, and continue directly
142
+ with the outer item `2.` (cancellation handling) that
143
+ immediately follows this iteration cycle `1.` within this
144
+ STEP 2.
142
145
 
143
146
  - If <result/> starts with `ERROR:`:
144
147
  Ask the user interactively, without a special tool, the
@@ -201,12 +201,18 @@ multi-*criteria* decision matrix.
201
201
  - Determine rating distance percentage <percentage/> between
202
202
  <alternative-K/> and <alternative-X/> from their *raw,
203
203
  unrounded* ratings as follows. If <rating-K/> is exactly zero,
204
- skip the division entirely and treat <percentage/> as if it
205
- were equal to <distance/> (so a true zero tie with <distance/>
206
- = 0 falls into the *MULTIPLE BEST* branch below, while a non-zero
207
- gap with zero best is routed by the magnitude of <distance/>
208
- through the same thresholds as <percentage/> otherwise). Do not
209
- output anything.
204
+ skip the division entirely (it is undefined against a zero
205
+ anchor) and classify directly by <distance/> instead of routing a
206
+ raw rating magnitude through the ratio thresholds below: if
207
+ <distance/> is also exactly zero, both best and second best rate
208
+ zero, which is a genuine tie, so set <percentage/> = 0 to fall
209
+ into the *MULTIPLE BEST* branch below; otherwise <distance/> > 0
210
+ means the second best rates strictly negative (since <rating-K/>
211
+ is the maximum), so there is a clear positive gap to a worse
212
+ runner-up and <alternative-K/> is an unambiguous winner, hence set
213
+ <percentage/> = 1 (a sentinel exceeding all small-distance
214
+ thresholds) to fall through to the plain *BEST ALTERNATIVE* branch.
215
+ Do not output anything.
210
216
 
211
217
  - Otherwise (<rating-K/> is non-zero), calculate: <percentage/> =
212
218
  <distance/> / abs(<rating-K/>). Do not output anything.
@@ -93,7 +93,7 @@ explicitly requested by this procedure via outputs based on a <template/>!
93
93
  Set <words-before><words/></words-before> (remember the loaded
94
94
  word count for the strictly-smaller check in step 3).
95
95
 
96
- <if condition="<content/> contains 'Created: <text/>'">
96
+ <if condition="<content/> contains 'Created: <text/>'">
97
97
  Set <timestamp-created><text/></timestamp-created> (extract the
98
98
  original creation timestamp so it can be re-inserted unchanged
99
99
  into the condensed <content/> in step 3).
@@ -280,12 +280,3 @@ explicitly requested by this procedure via outputs based on a <template/>!
280
280
  <template>
281
281
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan condensed -- hand-off to pre-flight**
282
282
  </template>
283
-
284
- - If <result/> matches `OTHER: <text/>` or is any other
285
- unrecognized value:
286
- Treat it as `DONE` (the condensed plan is already saved):
287
- only output the following <template/> and then *STOP*.
288
-
289
- <template>
290
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan condensed -- done**
291
- </template>
@@ -320,7 +320,8 @@ Set <content-dirty>true</content-dirty>.
320
320
  ISO-style format, which has to be determined by calling the
321
321
  `ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase`
322
322
  MCP server and use the `text` field of its response. Update
323
- <content/> with the new `Modified: <timestamp-modified/>`.
323
+ the `⚙ Modified: ...` line of <content/> with the new
324
+ `⚙ Modified: <timestamp-modified/>`.
324
325
  Do not output anything.
325
326
  </if>
326
327
 
@@ -213,7 +213,7 @@ explicitly requested by this procedure via outputs based on a <template/>!
213
213
 
214
214
  2. Finally, update the plan in <plan/> based on all answers <answer-N/>.
215
215
 
216
- 3. <if condition="<plan/> contains 'Created: <text/>'">
216
+ 3. <if condition="<plan/> contains 'Created: <text/>'">
217
217
  Set <timestamp-created><text/></timestamp-created> (set
218
218
  timestamp-created to extracted text)
219
219
  </if>
@@ -326,12 +326,3 @@ explicitly requested by this procedure via outputs based on a <template/>!
326
326
  <template>
327
327
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to implementation**
328
328
  </template>
329
-
330
- - If <result/> matches `OTHER: <text/>` or is any other
331
- unrecognized value:
332
- Treat it as `DONE` (the updated plan is already saved):
333
- only output the following <template/> and then *STOP*.
334
-
335
- <template>
336
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- done**
337
- </template>
@@ -28,7 +28,8 @@ Configure Task Id
28
28
 
29
29
  1. Determine request:
30
30
  <request><getopt-arguments/></request>
31
- Inherit the always existing <ase-session-id/> from the current context.
31
+ Inherit the always existing <ase-session-id/> and the current
32
+ <ase-task-id/> from the current context.
32
33
 
33
34
  2. <if condition="<request/> is NOT empty AND <request/> does NOT match the regexp `^[a-zA-Z][a-zA-Z0-9_-]*$`">
34
35
  Only output the following <template/> and then immediately
@@ -44,6 +45,8 @@ Configure Task Id
44
45
  tool from the `ase` MCP server and set <text/> to its
45
46
  `text` output. Check the response as mandated above; only
46
47
  on a clean response set <ase-task-id><text/></ase-task-id>.
48
+ On an `ERROR:`/`WARNING:` response, keep the <ase-task-id/>
49
+ inherited from the current context as the fallback.
47
50
 
48
51
  - Output:
49
52
  <template>
@@ -223,12 +223,3 @@ explicitly requested by this procedure via outputs based on a <template/>!
223
223
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan implemented -- hand-off to delete task**
224
224
  </template>
225
225
 
226
- - If <result/> matches `OTHER: <text/>` or is any other
227
- unrecognized value:
228
- Treat it as `DONE` (the plan is already implemented):
229
- only output the following <template/> and then *STOP*.
230
-
231
- <template>
232
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan implemented -- done**
233
- </template>
234
-
@@ -162,7 +162,8 @@ explicitly requested by this procedure via outputs based on a <template/>!
162
162
  ISO-style format, which has to be determined by calling the
163
163
  `ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the `ase`
164
164
  MCP server and use the `text` field of its response. Update
165
- <content/> with the new `Modified: <timestamp-modified/>`.
165
+ the `⚙ Modified: ...` line of <content/> with the new
166
+ `⚙ Modified: <timestamp-modified/>`.
166
167
  Do not output anything.
167
168
  </if>
168
169
 
@@ -251,12 +252,3 @@ explicitly requested by this procedure via outputs based on a <template/>!
251
252
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to implement**
252
253
  </template>
253
254
 
254
- - If <result/> matches `OTHER: <text/>` or is any other
255
- unrecognized value:
256
- Treat it as `DONE` (the plan is already preflighted):
257
- only output the following <template/> and then *STOP*.
258
-
259
- <template>
260
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- done**
261
- </template>
262
-
@@ -124,18 +124,35 @@ explicitly requested by this procedure via outputs based on a <template/>!
124
124
  (append extracted text to instruction).
125
125
  </if>
126
126
 
127
- 6. <if condition="<content/> contains 'Created: <text/>'">
127
+ 6. <if condition="<content/> contains 'Created: <text/>'">
128
128
  Set <timestamp-created><text/></timestamp-created> (set
129
129
  timestamp-created to extracted text)
130
130
  </if>
131
131
 
132
- 7. Create a new plan from scratch and store the result as
132
+ 7. <if condition="<instruction/> is empty or contains only whitespace">
133
+ The WHAT/WHY extraction yielded no usable text (e.g. a
134
+ `- **WHAT**:` line existed but captured empty text, so the
135
+ whole-content fallback above did not fire). Fall back to the
136
+ full previous plan content: set <instruction><content/></instruction>
137
+ (set instruction to content).
138
+ <if condition="<instruction/> is still empty or contains only whitespace">
139
+ There is nothing to reboot from. Only output the following
140
+ <template/> and then immediately *STOP* processing the entire
141
+ current skill:
142
+
143
+ <template>
144
+ ⧉ **ASE**: ☻ skill: **ase-task-reboot**, ▶ ERROR: empty instruction -- nothing to reboot from
145
+ </template>
146
+ </if>
147
+ </if>
148
+
149
+ 8. Create a new plan from scratch and store the result as
133
150
  <content/> by closely following the defined plan format
134
151
  <format/> and injecting into it all the information from
135
152
  the <instruction/> and all decisions you derived from the
136
153
  <instruction/>.
137
154
 
138
- 8. Call the `ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the
155
+ 9. Call the `ase_timestamp(format: "yyyy-LL-dd HH:mm")` tool of the
139
156
  `ase` MCP server and use the `text` field of its response for
140
157
  <timestamp-modified/> information. If <timestamp-created/> is
141
158
  still unset (because the previous <content/> had no `Created:`
@@ -145,12 +162,12 @@ explicitly requested by this procedure via outputs based on a <template/>!
145
162
  <timestamp-modified/> information and calculate the number of
146
163
  words <words/> of <content/>.
147
164
 
148
- 9. Call the `ase_task_save(id: "<ase-task-id/>",
165
+ 10. Call the `ase_task_save(id: "<ase-task-id/>",
149
166
  text: "<content/>")` of the `ase` MCP server to save the updated
150
167
  task plan content. Do not output anything related to this MCP
151
168
  call.
152
169
 
153
- 10. Only output the following <template/> and continue processing:
170
+ 11. Only output the following <template/> and continue processing:
154
171
 
155
172
  <template>
156
173
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan rebooted**
@@ -244,12 +261,3 @@ explicitly requested by this procedure via outputs based on a <template/>!
244
261
  ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- hand-off to pre-flight**
245
262
  </template>
246
263
 
247
- - If <result/> matches `OTHER: <text/>` or is any other
248
- unrecognized value:
249
- Treat it as `DONE` (the updated plan is already saved):
250
- only output the following <template/> and then *STOP*.
251
-
252
- <template>
253
- ⧉ **ASE**: ◉ task: **<ase-task-id/>**, ✪ plan: **<words/>** words, ▶ status: **plan updated -- done**
254
- </template>
255
-