@rse/ase 0.9.12 → 0.9.14

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.
Files changed (44) hide show
  1. package/dst/ase-compat.js +1 -1
  2. package/dst/ase-diagram.js +5 -2
  3. package/dst/ase-getopt.js +2 -1
  4. package/dst/ase-kv.js +30 -11
  5. package/dst/ase-mcp.js +2 -0
  6. package/dst/ase-persona.js +4 -1
  7. package/dst/ase-skills.js +13 -3
  8. package/dst/ase-statusline.js +2 -2
  9. package/dst/ase.js +1 -1
  10. package/package.json +1 -1
  11. package/plugin/.claude-plugin/plugin.json +1 -1
  12. package/plugin/.codex-plugin/plugin.json +1 -1
  13. package/plugin/.github/plugin/plugin.json +1 -1
  14. package/plugin/agents/ase-meta-chat.md +3 -3
  15. package/plugin/meta/ase-dialog.md +1 -1
  16. package/plugin/meta/ase-format-arch.md +26 -9
  17. package/plugin/meta/ase-format-meta.md +7 -3
  18. package/plugin/meta/ase-format-spec.md +11 -6
  19. package/plugin/meta/ase-getopt.md +8 -3
  20. package/plugin/meta/ase-skill.md +49 -21
  21. package/plugin/package.json +1 -1
  22. package/plugin/skills/ase-arch-analyze/SKILL.md +26 -13
  23. package/plugin/skills/ase-arch-discover/SKILL.md +25 -14
  24. package/plugin/skills/ase-code-analyze/SKILL.md +10 -5
  25. package/plugin/skills/ase-code-craft/SKILL.md +16 -4
  26. package/plugin/skills/ase-code-explain/SKILL.md +0 -1
  27. package/plugin/skills/ase-code-insight/SKILL.md +1 -1
  28. package/plugin/skills/ase-code-lint/SKILL.md +56 -18
  29. package/plugin/skills/ase-code-refactor/SKILL.md +16 -4
  30. package/plugin/skills/ase-code-resolve/SKILL.md +17 -4
  31. package/plugin/skills/ase-docs-proofread/SKILL.md +48 -17
  32. package/plugin/skills/ase-meta-brainstorm/SKILL.md +5 -2
  33. package/plugin/skills/ase-meta-compat/SKILL.md +2 -12
  34. package/plugin/skills/ase-meta-diff/SKILL.md +3 -3
  35. package/plugin/skills/ase-meta-evaluate/SKILL.md +32 -24
  36. package/plugin/skills/ase-meta-quorum/SKILL.md +3 -3
  37. package/plugin/skills/ase-task-condense/SKILL.md +6 -9
  38. package/plugin/skills/ase-task-edit/SKILL.md +6 -2
  39. package/plugin/skills/ase-task-grill/SKILL.md +9 -15
  40. package/plugin/skills/ase-task-id/SKILL.md +7 -4
  41. package/plugin/skills/ase-task-implement/SKILL.md +6 -11
  42. package/plugin/skills/ase-task-preflight/SKILL.md +5 -10
  43. package/plugin/skills/ase-task-reboot/SKILL.md +21 -13
  44. package/plugin/skills/ase-task-rename/SKILL.md +9 -0
package/dst/ase-compat.js CHANGED
@@ -12,7 +12,7 @@ const EXPECTED = {
12
12
  "xml-placeholders/overwrite": "99",
13
13
  "xml-placeholders/indexed": "+1,-1",
14
14
  "xml-placeholders/nested-attr": "20",
15
- "xml-placeholders/entity": "",
15
+ "xml-placeholders/entity": "⦿",
16
16
  /* control-flow */
17
17
  "control-flow/branch": "mid",
18
18
  "control-flow/while-sum": "15",
@@ -117,12 +117,15 @@ export class Diagram {
117
117
  /* detect terminal color capability */
118
118
  static detectColorMode() {
119
119
  let mode = "none";
120
+ let explicit = false;
120
121
  /* attempt 1: query environment variable (explicitly) */
121
122
  if (process.env.ASE_TERM_COLORS !== undefined)
122
- if (/^(?:none|ansi16|ansi256)$/.test(process.env.ASE_TERM_COLORS))
123
+ if (/^(?:none|ansi16|ansi256)$/.test(process.env.ASE_TERM_COLORS)) {
123
124
  mode = process.env.ASE_TERM_COLORS;
125
+ explicit = true;
126
+ }
124
127
  /* attempt 2: query stdout */
125
- if (mode === "none" && process.stdout.isTTY) {
128
+ if (!explicit && process.stdout.isTTY) {
126
129
  const depth = process.stdout.getColorDepth();
127
130
  if (depth >= 8)
128
131
  mode = "ansi256";
package/dst/ase-getopt.js CHANGED
@@ -108,7 +108,8 @@ export class GetoptMCP {
108
108
  if (listOpts.length > 0) {
109
109
  const opts = cmd.opts();
110
110
  for (const { long, choices } of listOpts) {
111
- const v = opts[long];
111
+ const key = long.replace(/-(.)/g, (_, c) => c.toUpperCase());
112
+ const v = opts[key];
112
113
  if (typeof v !== "string")
113
114
  continue;
114
115
  const items = v.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
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
@@ -133,6 +133,8 @@ export default class MCPCommand {
133
133
  };
134
134
  /* trigger a reconnect chain (idempotent while one is active) */
135
135
  const triggerReconnect = (reason) => {
136
+ if (reconnecting)
137
+ return;
136
138
  reconnecting = true;
137
139
  this.log.write("warning", `mcp: ${reason} — reconnecting`);
138
140
  reconnect(0).catch(() => { });
@@ -21,7 +21,10 @@ export class Persona {
21
21
  const val = cfg.get("agent.persona");
22
22
  if (val === undefined)
23
23
  return "engineer";
24
- return String(isScalar(val) ? val.value : val);
24
+ const style = String(isScalar(val) ? val.value : val);
25
+ if (!Persona.styles.includes(style))
26
+ return "engineer";
27
+ return style;
25
28
  }
26
29
  /* set the persona style on the strongest scope of an optional session */
27
30
  static set(log, style, session) {
package/dst/ase-skills.js CHANGED
@@ -263,10 +263,20 @@ export class Skills {
263
263
  const halfLife = 365 / 2;
264
264
  /* lifespan requires both timestamps; recentness requires the
265
265
  updated timestamp -- any unavailable date-derived factor is
266
- treated as neutral `1` so the entry can still be ranked by the
267
- remaining metrics, instead of collapsing the product to zero */
266
+ given a neutral default so the entry can still be ranked by the
267
+ remaining metrics, instead of collapsing the product to zero.
268
+ For `lifespan` (an unbounded duration) the multiplicative-neutral
269
+ `1` is also the low floor, so a missing value cannot unfairly
270
+ inflate the rank. For `recentness`, however, the value is an
271
+ exp-decay factor bounded in `(0,1]` where `1` is the *ceiling*
272
+ (a just-now update), not a floor -- defaulting a missing update
273
+ date to `1` would hand packages with absent metadata the highest
274
+ possible recentness and let them outrank genuinely fresh ones.
275
+ We therefore default a missing `recentness` to the decay value at
276
+ one half-life (`0.5`), a conservative midpoint that keeps the
277
+ entry rankable without rewarding the missing date. */
268
278
  const lifespan = (!Number.isNaN(cMs) && !Number.isNaN(uMs)) ? Math.max(0, uMs - cMs) : 1;
269
- const recentness = !Number.isNaN(uMs) ? Math.exp(-Math.max(0, (now - uMs) / msPerDay) / halfLife) : 1;
279
+ const recentness = !Number.isNaN(uMs) ? Math.exp(-Math.max(0, (now - uMs) / msPerDay) / halfLife) : 0.5;
270
280
  return d * s * lifespan * recentness;
271
281
  }
272
282
  /* compute the per-alternative product-sum (rating) row from a
@@ -67,7 +67,7 @@ const formatTokens = (n) => {
67
67
  if (n >= 1_000_000)
68
68
  return `${(n / 1_000_000).toFixed(1)}M`;
69
69
  if (n >= 1_000)
70
- return `${Math.round(n / 1_000)}k`;
70
+ return `${(n / 1_000).toFixed(1)}k`;
71
71
  return `${n}`;
72
72
  };
73
73
  /* format a millisecond duration as a compact human-readable string (e.g. 6d 12hr 7m, 4hr 27m, 12m 30s) */
@@ -362,7 +362,7 @@ export default class StatuslineCommand {
362
362
  emit(`${prefix("◔", "context")}${bar} ${pct}%`);
363
363
  },
364
364
  C: () => {
365
- const pct = Math.floor(data.context_window?.used_percentage ?? 0);
365
+ const pct = data.context_window?.used_percentage ?? 0;
366
366
  const tokensCur = (data.context_window?.total_input_tokens ?? 0) +
367
367
  (data.context_window?.total_output_tokens ?? 0);
368
368
  const tokensLim = pct > 0 && tokensCur > 0 ? Math.round(tokensCur * 100 / pct) : 0;
package/dst/ase.js CHANGED
@@ -18,7 +18,7 @@ import ArtifactCommand from "./ase-artifact.js";
18
18
  import CompatCommand from "./ase-compat.js";
19
19
  import pkg from "../package.json" with { type: "json" };
20
20
  /* globally initialize logger */
21
- const log = new Log("ase", "warning", "-");
21
+ const log = new Log("ase", "info", "-");
22
22
  /* main entry point (wrapped in a regular async function to avoid
23
23
  top-level await, which would be reported as "unsettled" by Node in
24
24
  the long-running daemon process spawned by "ase service start") */
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.12",
9
+ "version": "0.9.14",
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.12",
3
+ "version": "0.9.14",
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.12",
3
+ "version": "0.9.14",
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.12",
3
+ "version": "0.9.14",
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).
@@ -185,7 +185,7 @@ following procedure:
185
185
 
186
186
  Do not output anything in this step!
187
187
 
188
- 2. Dispatch according to the agent tool:
188
+ 2. Render the custom dialog, collect the user input, and dispatch on the result:
189
189
 
190
190
  1. You *MUST* not output anything in this step.
191
191
 
@@ -243,7 +243,7 @@ at runtime.
243
243
  for the component's existence and boundary.
244
244
 
245
245
  - In case a component has no dependencies at all, the
246
- entire `- Depends On:` bullet point is omitted.
246
+ entire `- Depends On:` bullet point is omitted.
247
247
 
248
248
  - In case the rationale is not present, the
249
249
  entire `, **BECAUSE** [...]` clause is omitted.
@@ -666,8 +666,16 @@ How the Non-Functional Requirements (NR) are addressed.
666
666
  perspective addresses, one of `Performance`, `Scalability`,
667
667
  `Reliability`, `Availability`, `Security`, `Privacy`,
668
668
  `Usability`, `Accessibility`, `Maintainability`, `Portability`,
669
- `Compatibility`, or `Compliance` (aligned with the Non-Functional
670
- Requirements categories).
669
+ `Compatibility`, or `Compliance`. This enumeration is a
670
+ solution-oriented refinement of the Non-Functional Requirements
671
+ (NR) categories and intentionally differs from them: the NR
672
+ categories follow the ISO/IEC 25010:2023 top-level characteristics
673
+ (and include `Safety` and `Flexibility`), whereas the perspective
674
+ qualities decompose some of these into the finer-grained,
675
+ architecturally actionable attributes `Scalability`,
676
+ `Availability`, `Privacy`, `Accessibility`, and `Portability`. A
677
+ perspective's quality therefore need not match the category of the
678
+ `SPEC-NR-<spec-nr-requirement-id/>` it addresses.
671
679
 
672
680
  - <arch-qp-perspective-requirement/> is a
673
681
  `SPEC-NR-<spec-nr-requirement-id/>` reference to the corresponding
@@ -722,10 +730,11 @@ forces at play, the chosen response, and the reasoning that justifies it.
722
730
 
723
731
  ## DECISION: <arch-dr-decision-name/> <a id="ARCH-DR-<arch-dr-decision-id/>"></a>
724
732
 
725
- - Status: <arch-dr-decision-status/>
726
- - Affects: <arch-dr-decision-element/>, [...]
727
- - Created: <arch-dr-decision-created/>
728
- - Modified: <arch-dr-decision-modified/>
733
+ - Status: <arch-dr-decision-status/>
734
+ - Affects: <arch-dr-decision-element/>, [...]
735
+ - Superseded-By: <arch-dr-decision-superseded-by/>
736
+ - Created: <arch-dr-decision-created/>
737
+ - Modified: <arch-dr-decision-modified/>
729
738
 
730
739
  - WHEN (Context):
731
740
  <arch-dr-decision-context/>
@@ -752,12 +761,17 @@ forces at play, the chosen response, and the reasoning that justifies it.
752
761
  <arch-dr-decision-decision/>, not longer than 80 characters.
753
762
 
754
763
  - <arch-dr-decision-status/> is one of `proposed`, `accepted`,
755
- `deprecated`, or `superseded by ARCH-DR-<arch-dr-decision-id/>`.
764
+ `deprecated`, or `superseded`.
756
765
 
757
766
  - <arch-dr-decision-element/> is an `ARCH-FV-<arch-fv-component-id/>`
758
767
  or `ARCH-DP-<arch-dp-node-id/>` reference to the functional
759
768
  element or deployment node the decision affects.
760
769
 
770
+ - <arch-dr-decision-superseded-by/> is an
771
+ `ARCH-DR-<arch-dr-decision-id/>` reference to the decision that
772
+ supersedes this decision. It is present if and only if the
773
+ <arch-dr-decision-status/> is `superseded`.
774
+
761
775
  - <arch-dr-decision-created/> is the timestamp when this decision was
762
776
  created, and <arch-dr-decision-modified/> is the timestamp when
763
777
  this decision was last modified, both in the same ISO-style format
@@ -832,7 +846,10 @@ forces at play, the chosen response, and the reasoning that justifies it.
832
846
  job.
833
847
 
834
848
  - In case the element reference is not present, the
835
- entire `- Affects:` bullet point is omitted.
849
+ entire `- Affects:` bullet point is omitted.
850
+
851
+ - In case the <arch-dr-decision-status/> is not `superseded`, the
852
+ entire `- Superseded-By:` bullet point is omitted.
836
853
 
837
854
  - In case the `NOTES (Background)` content is not present, the
838
855
  entire `NOTES (Background)` chunk is omitted.
@@ -29,9 +29,13 @@ Artifact Meta Information
29
29
  - **Artifact**:
30
30
 
31
31
  At level 2, each **Artifact Set** is composed of many **Artifact**s.
32
- Each **Artifact** has a unique identifier <artifact-id/>, which is
33
- an upper-case, two-letter identifier (e.g. `CJ` for `Customer
34
- Journey`) derived from the **Artifact** name.
32
+ Each **Artifact** has an identifier <artifact-id/>, which is an
33
+ upper-case, two-letter identifier (e.g. `CJ` for `Customer Journey`)
34
+ derived from the **Artifact** name. The <artifact-id/> is unique
35
+ only *within* its **Artifact Set**; the globally-unique handle of an
36
+ **Artifact** is the qualified form <artifact-set-id/>-<artifact-id/>
37
+ (e.g. `SPEC-DP` and `ARCH-DP` are distinct artifacts). All
38
+ references to an **Artifact** *MUST* use this qualified form.
35
39
 
36
40
  Each **Artifact** also has a sequence number <artifact-no/>, which
37
41
  is the zero-padded, two-digit position of the **Artifact** (starting
@@ -871,8 +871,8 @@ for main, alternative, and exceptional paths.
871
871
 
872
872
  - <spec-uc-usecase-description/>: a concise paragraph (1-3
873
873
  sentences) of prose describing the use case at a glance, without
874
- prescribing the step-by-step flow (which belongs to the **Use
875
- Case Scenarios** **Artifact**).
874
+ prescribing the step-by-step flow (which belongs to the
875
+ **SCENARIO** blocks of this use case).
876
876
 
877
877
  - In case the use case realizes no specific functional requirement,
878
878
  the entire `- Requirements:` bullet point is omitted.
@@ -884,7 +884,7 @@ for main, alternative, and exceptional paths.
884
884
 
885
885
  <format>
886
886
 
887
- ### SCENARIO: <spec-uc-usecase-scenario-name/> (<spec-uc-usecase-scenario-type/>)
887
+ ### SCENARIO: <spec-uc-usecase-scenario-name/> (<spec-uc-usecase-scenario-type/>) <a id="SPEC-UC-<spec-uc-usecase-id/>-<spec-uc-usecase-scenario-id/>"></a>
888
888
 
889
889
  1. <spec-uc-usecase-scenario-step/>
890
890
  2. <spec-uc-usecase-scenario-step/>
@@ -894,6 +894,11 @@ for main, alternative, and exceptional paths.
894
894
 
895
895
  - <spec-uc-usecase-scenario/> details:
896
896
 
897
+ - <spec-uc-usecase-scenario-id/>: per-use-case unique "slug" of
898
+ always 1-3 lower-cased words (concatenated with "-" characters
899
+ and in total not longer than 30 characters), derived from
900
+ <spec-uc-usecase-scenario-name/>.
901
+
897
902
  - <spec-uc-usecase-scenario-name/>: a short (3-8 word) summary of the
898
903
  scenario.
899
904
 
@@ -1213,9 +1218,9 @@ turn.
1213
1218
  visualizes (optional).
1214
1219
 
1215
1220
  - <spec-ds-storyboard-scenario/> is a
1216
- Use Case scenario type `<spec-uc-usecase-scenario-type/>` of
1217
- the corresponding **Aspect** of the Use Case **Artifact** the
1218
- storyboard visualizes (optional).
1221
+ `SPEC-UC-<spec-uc-usecase-id/>-<spec-uc-usecase-scenario-id/>`
1222
+ reference to the corresponding scenario **Aspect** of the Use
1223
+ Case **Artifact** the storyboard visualizes (optional).
1219
1224
 
1220
1225
  - <spec-ds-frame-name/>: a short (2-5 word) label for the screen,
1221
1226
  turn, or state depicted by the storyboard frame. Frames are
@@ -27,8 +27,8 @@ set placeholders into the context as a side-effect.
27
27
  `--<long/>[|-<short/>][=<default/>|=(<c1/>|<c2/>|...)[...]]`, set
28
28
  <getopt-option-<long/>/> to <default/> (for `=<default/>`
29
29
  form), or to the *single token* <c1/> (the first choice, both
30
- for the choice form `=(<c1/>|<c2>/|...)` and for the list form
31
- `=(<c1/>|<c2>/|...)...` -- an *unsupplied* list-option always
30
+ for the choice form `=(<c1/>|<c2/>|...)` and for the list form
31
+ `=(<c1/>|<c2/>|...)...` -- an *unsupplied* list-option always
32
32
  defaults to the single token <c1/>, *not* to a whole list),
33
33
  or to `false` (for value-less options). Then set
34
34
  <getopt-arguments><getopt-args/></getopt-arguments>.
@@ -63,6 +63,10 @@ set placeholders into the context as a side-effect.
63
63
  validate each remaining token themselves as they consume it).
64
64
 
65
65
  4. **Short-Circuit for Error**:
66
+ This step is the *sole* and *authoritative* handler for an
67
+ `ERROR:`-prefixed `ase_getopt` response; the generic MCP-Tool-Calls
68
+ error rule (see `ase-skill.md`) explicitly does *NOT* apply here, so
69
+ only the <template/> below is emitted (never both).
66
70
  If <text/> starts with `ERROR:`:
67
71
  Remove all `ERROR:` or `error:` prefixes from <text/>.
68
72
  Then only output the following <template/> and
@@ -92,7 +96,8 @@ set placeholders into the context as a side-effect.
92
96
  `<getopt-result/>.opts[<longN/>]`.
93
97
  Set <getopt-arguments/> to the value of `<getopt-result/>.args`.
94
98
  Set <getopt-info/> to `<getopt-result/>.info`, but remove
95
- information about the `help` option.
99
+ information about the `help` option and any *internal* option
100
+ whose long name starts with `int-`.
96
101
 
97
102
  7. **Display Results**:
98
103
  Just output the following <template/>:
@@ -119,15 +119,25 @@ Skill Sequential Processing
119
119
  For speed, emit *all* `TaskCreate` calls together in a *single* turn
120
120
  (issued in parallel), *not* one-per-turn sequentially. Do *not*
121
121
  rely on the call order to establish the step order, as the parallel
122
- results carry no guaranteed ordering. Instead, in the *immediately
123
- following* turn, establish the strict order explicitly by chaining
124
- the created tasks with `TaskUpdate`: for each <step/> after the
125
- first one, call `TaskUpdate({ taskId: "<this/>", addBlockedBy:
126
- [ "<prev/>" ] })` so that every step (with `taskId` <this/>) is
127
- blocked by its predecessor step (with `taskId` <prev/>).
122
+ results carry no guaranteed ordering.
123
+
124
+ Instead, in the *immediately single following* turn, first
125
+ reconstruct the step-id-to-taskId mapping: because each task's
126
+ `subject` equals the originating <step/> `id` (and <step/> ids are
127
+ unique), match every created task back to its <step/> by comparing
128
+ the task's `subject` to the step `id` -- read the ids straight from
129
+ the `TaskCreate` results, or call `TaskList` if the results are
130
+ no longer at hand.
131
+
132
+ Then establish the strict order explicitly by chaining the created
133
+ tasks with `TaskUpdate`: for each <step/> after the first one,
134
+ resolve <this/> and <prev/> to the mapped `taskId` values and
135
+ call `TaskUpdate({ taskId: "<this/>", addBlockedBy: [ "<prev/>" ]
136
+ })` so that every step (with `taskId` <this/>) is blocked by its
137
+ predecessor step (with `taskId` <prev/>).
128
138
 
129
139
  - *IMPORTANT*: For each <step/> you *MUST* use the `TaskUpdate` tool
130
- for updating its status during processing.
140
+ for updating its status *during* processing, once a <step/> finished.
131
141
 
132
142
  - *IMPORTANT*: You *MUST* sequentially execute every <step/> in
133
143
  a <flow/> *EXACTLY* as the instructions specify.
@@ -172,6 +182,12 @@ MCP Tool Calls
172
182
  ⧉ **ASE**: **ERROR:** MCP tool failed: <info/>
173
183
  </template>
174
184
 
185
+ *EXCEPTION*: this rule does *NOT* apply to the `ase_getopt` tool,
186
+ whose `ERROR:`-prefixed output is *expected* option-parsing
187
+ feedback handled exclusively by step 4 of the `getopt` procedure
188
+ (see `ase-getopt.md`); never emit this generic template for an
189
+ `ase_getopt` response.
190
+
175
191
  - Only on a clean response: proceed as the skill instructs.
176
192
 
177
193
  Skill Identification
@@ -232,9 +248,12 @@ Template Patterns
232
248
  </template>
233
249
 
234
250
  - When `<ase-tpl-head title="<title/>"/>` should be expanded, use
235
- (where <title-len/> is the number of characters in the string
236
- `⧉ ASE: <title/>`, and <bar/> is the `─` character repeated exactly
237
- (67 - <title-len/>) times):
251
+ (where <raw-title/> is the visible un-styled text `⧉ ASE: <title/>`,
252
+ <raw-title-len/> is the number of characters in <raw-title/>, 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
256
+ `<ase-tpl-boxed/>`, so equal visible text yields equal total width):
238
257
 
239
258
  <template>
240
259
 
@@ -251,9 +270,12 @@ Template Patterns
251
270
  </template>
252
271
 
253
272
  - When `<ase-tpl-foot title="<title/>"/>` should be expanded, use
254
- (where <title-len/> is the number of characters in the string
255
- `⧉ ASE: <title/>`, and <bar/> is the `─` character repeated exactly
256
- (67 - <title-len/>) times):
273
+ (where <raw-title/> is the visible un-styled text `⧉ ASE: <title/>`,
274
+ <raw-title-len/> is the number of characters in <raw-title/>, 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
278
+ `<ase-tpl-boxed/>`, so equal visible text yields equal total width):
257
279
 
258
280
  <template>
259
281
 
@@ -275,19 +297,24 @@ Template Patterns
275
297
  </else>
276
298
 
277
299
  - When `<ase-tpl-boxed title="<title/>"[ subtitle="<subtitle/>"]><content/></ase-tpl-boxed>`
278
- should be expanded use the following helper placeholder and then the <template/>:
300
+ should be expanded use the following helper placeholders and then
301
+ the following <template/>:
279
302
 
280
303
  - <if condition="<subtitle/> is not empty">
281
- Set <raw-title>⧉ ASE: <title/>: <subtitle/><raw-title>.
304
+ Set <raw-title>⧉ ASE: <title/>: <subtitle/></raw-title>.
282
305
  Set <render-title>⧉ ASE: **`<title/>`**: `<subtitle/>`</render-title>.
283
306
  </if>
284
307
  <else>
285
- Set <raw-title>⧉ ASE: <title/><raw-title>.
308
+ Set <raw-title>⧉ ASE: <title/></raw-title>.
286
309
  Set <render-title>⧉ ASE: **`<title/>`**</render-title>.
287
310
  </else>
288
- - Set <raw-title-len/> to the number of characters in <raw-title/>.
289
- - Set <bar/> to the `─` character repeated exactly (67 - <raw-title-len/>) times.
290
- - Set <body> to <content/> with all line-starts prefixed with `│ `.
311
+ - Set <raw-title-len/> to the number of characters in the visible
312
+ un-styled text <raw-title/>.
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
316
+ `<ase-tpl-foot/>`, so equal visible text yields equal total width.
317
+ - Set <body/> to <content/> with all line-starts prefixed with `│ `.
291
318
 
292
319
  <template>
293
320
 
@@ -324,7 +351,8 @@ Template Patterns
324
351
  <template>🟠</template>
325
352
 
326
353
  - When `<ase-tpl-pad width="<width/>" text="<text/>"/>` should be expanded, use
327
- (where <ws/> = ` ` x (<width/> - length("<text/>")), i.e., <ws/> is
328
- 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):
329
357
 
330
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.12",
9
+ "version": "0.9.14",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -375,13 +375,19 @@ interface quality, quality attributes, and architecture governance.
375
375
  - *Focal aspect*: <focal-aspect/> - <focal-state/>
376
376
  - *In tension with*: <partner-list/>
377
377
 
378
- **RECOMMENDED**: lean toward *<focal|partners/>*
378
+ **RECOMMENDED**: lean toward *<lean-toward/>*
379
379
  *Reason*: <rationale/>
380
380
  *Implies*:
381
- - <partner-aspect-1/>: <partner-implication-1/>
382
- - <partner-aspect-2/>: <partner-implication-2/>
381
+ - <partner-aspect/>: <partner-implication/>
382
+ - ...
383
383
  </template>
384
384
 
385
+ For each partner in <partner-list/>, repeat the `*Implies*` line,
386
+ set <partner-aspect/> to the partner aspect, and set
387
+ <partner-implication/> to its brief implication. Set <lean-toward/>
388
+ to the aspect direction the recommendation favors (the *focal
389
+ aspect* or the *partners*).
390
+
385
391
  Hints:
386
392
 
387
393
  - For the final results, do *not* output anything else,
@@ -393,9 +399,13 @@ interface quality, quality attributes, and architecture governance.
393
399
  DEPENDENCY-DIRECTION`).
394
400
 
395
401
  - The <focal-aspect/> is the aspect that participates in
396
- *all* tensions of the cluster. For a size-2 cluster, pick
397
- the aspect whose direction is most constrained by the
398
- 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.
399
409
 
400
410
  - *Brevity and precision*: all free-form placeholders
401
411
  (<description/>, <focal-state/>, partner-implications)
@@ -429,13 +439,16 @@ interface quality, quality attributes, and architecture governance.
429
439
  for the same aspect, and never emit both halves of a
430
440
  tension pair as separate PROBLEMs.
431
441
 
432
- - *Additionally*, first call the `ase_kv_clear()` tool of the `ase`
433
- MCP server to clear the in-memory key/value store, and then,
434
- for *every* reported PROBLEM and TRADEOFF, persist its finding
435
- result via the `ase_kv_set` tool of the `ase` MCP server, using
436
- `key` set to `ase-issue-P<n/>` (for PROBLEMs) or
437
- `ase-issue-T<n/>` (for TRADEOFFs) and `val` set to
438
- `<title/>: <description/>`.
442
+ - *Additionally*, persist all reported findings in a *single*
443
+ `ase_kv_batch` call to the `ase` MCP server with `transactional`
444
+ set to `true`. The `commands` parameter array of this call
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
450
+ `{ command: "set", key: "ase-issue-T<n/>", val: "<title/>:
451
+ <description/>" }` entry per reported TRADEOFF.
439
452
  </step>
440
453
 
441
454
  4. <step id="STEP 4: Give Final Hint">