@rse/ase 1.0.5 → 1.0.6

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-hook.js CHANGED
@@ -208,9 +208,11 @@ export default class HookCommand {
208
208
  /* determine path to external files */
209
209
  const filePkg = path.join(pluginRoot, ".claude-plugin", "plugin.json");
210
210
  const fileMd = path.join(pluginRoot, "meta", "ase-constitution.md");
211
+ const fileStyle = path.join(pluginRoot, "output-styles", "ase-terse.md");
211
212
  /* read external files */
212
213
  let pkg;
213
214
  let md;
215
+ let style;
214
216
  try {
215
217
  pkg = fs.readFileSync(filePkg, "utf8");
216
218
  }
@@ -223,6 +225,12 @@ export default class HookCommand {
223
225
  catch (err) {
224
226
  throw new Error(`failed to read constitution file: ${fileMd}`, { cause: err });
225
227
  }
228
+ try {
229
+ style = fs.readFileSync(fileStyle, "utf8");
230
+ }
231
+ catch (err) {
232
+ throw new Error(`failed to read output style file: ${fileStyle}`, { cause: err });
233
+ }
226
234
  /* determine own version */
227
235
  const pkgObj = this.parseJSON(pkg, v.object({ version: v.optional(v.string()) }));
228
236
  const versionCurrentPlugin = pkgObj.version ?? "";
@@ -340,6 +348,12 @@ export default class HookCommand {
340
348
  "\n" + md;
341
349
  /* expand all @<file> references manually */
342
350
  md = this.expandReferences(md, path.dirname(fileMd));
351
+ /* append the output style to the constitution markdown (GitHub
352
+ Copilot CLI and OpenAI Codex CLI only -- Anthropic Claude Code CLI
353
+ applies the plugin output style natively), stripping the
354
+ YAML frontmatter which only Anthropic Claude Code CLI understands */
355
+ if (tool !== "claude")
356
+ md += "\n" + style.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
343
357
  /* build the deterministic ASE banner (rendered directly by the
344
358
  agent harness, independent of any model decision, so it is
345
359
  guaranteed to appear once in every non-headless session;
package/dst/ase-setup.js CHANGED
@@ -143,6 +143,9 @@ export default class SetupCommand {
143
143
  const scopeArgs = tool === "claude" && scope !== "user" ? ["--scope", scope] : [];
144
144
  await this.run(spec.cli, ["plugin", "marketplace", "add", source, ...scopeArgs]);
145
145
  await this.run(spec.cli, ["plugin", spec.pInstall, "ase@ase", ...scopeArgs], { retries: 3 });
146
+ /* select the plugin-shipped ASE output style (Anthropic Claude Code CLI only) */
147
+ if (tool === "claude")
148
+ await this.outputStyleActivate(`install${dev ? "[dev]" : ""}`, scope);
146
149
  return 0;
147
150
  }
148
151
  /* handler for "ase setup update" (both tools) */
@@ -197,6 +200,9 @@ export default class SetupCommand {
197
200
  await this.run(spec.cli, ["plugin", spec.pInstall, "ase@ase", ...scopeArgs], { retries: 3 });
198
201
  }
199
202
  }
203
+ /* select the plugin-shipped ASE output style (Anthropic Claude Code CLI only) */
204
+ if (tool === "claude")
205
+ await this.outputStyleActivate(`update${dev ? "[dev]" : ""}`, scope);
200
206
  return 0;
201
207
  }
202
208
  /* handler for "ase setup enable|disable" (both tools) */
@@ -227,6 +233,9 @@ export default class SetupCommand {
227
233
  this.log.write("info", `setup: uninstall${dev ? "[dev]" : ""}: ` +
228
234
  "stopping potentially running ASE service");
229
235
  await this.run("ase", ["service", "stop"], { quiet: true, ignoreError: "ASE service not running" });
236
+ /* deselect the plugin-shipped ASE output style (Anthropic Claude Code CLI only) */
237
+ if (tool === "claude")
238
+ await this.outputStyleDeactivate(`uninstall${dev ? "[dev]" : ""}`, scope);
230
239
  /* uninstall ASE plugin */
231
240
  this.log.write("info", `setup: uninstall${dev ? "[dev]" : ""}: ` +
232
241
  `uninstalling ASE ${spec.label} plugin (origin: ${dev ? "local" : "remote/bundled"})`);
@@ -263,6 +272,9 @@ export default class SetupCommand {
263
272
  continue;
264
273
  rows.push(["MCP", chalk.bold(this.mcpServers[i].server), scope, "registered"]);
265
274
  }
275
+ /* report the ASE output style selections */
276
+ for (const outputStyle of await this.outputStyleStatus(tool))
277
+ rows.push(["OUTPUTSTYLE", chalk.bold(outputStyle.file), outputStyle.scope, outputStyle.status]);
266
278
  /* report the ASE statusline registrations */
267
279
  for (const statusline of await this.statuslineStatus(tool))
268
280
  rows.push(["STATUSLINE", chalk.bold(statusline.file), statusline.scope, statusline.status]);
@@ -660,7 +672,7 @@ export default class SetupCommand {
660
672
  "%P %h %B %c"
661
673
  ];
662
674
  /* resolve the tool settings file for a given installation scope */
663
- statuslineSettingsFile(tool, scope) {
675
+ settingsFile(tool, scope) {
664
676
  if (tool === "copilot") {
665
677
  const home = process.env.COPILOT_HOME ?? "";
666
678
  const base = home !== "" ? home : path.join(os.homedir(), ".copilot");
@@ -716,11 +728,11 @@ export default class SetupCommand {
716
728
  }
717
729
  return false;
718
730
  }
719
- /* locate the top-level "statusLine" member node within the root object */
720
- statuslineFindMember(root) {
731
+ /* locate a top-level member node by key within the root object */
732
+ settingsFindMember(root, name) {
721
733
  for (const m of root.query("/ member")) {
722
734
  const key = m.query("/ string [ pos() == 1 ]")[0];
723
- if (key !== undefined && key.get("value") === "statusLine")
735
+ if (key !== undefined && key.get("value") === name)
724
736
  return m;
725
737
  }
726
738
  return undefined;
@@ -749,7 +761,7 @@ export default class SetupCommand {
749
761
  return root.create("member").add(key, obj);
750
762
  }
751
763
  /* normalize the previous-last member so a following ",\n " comma reads cleanly */
752
- statuslineMakeNonLast(member) {
764
+ settingsMakeNonLast(member) {
753
765
  const val = member.query("/ * [ pos() == 2 ]")[0];
754
766
  if (val !== undefined) {
755
767
  const ep = val.get("epilog");
@@ -758,8 +770,42 @@ export default class SetupCommand {
758
770
  }
759
771
  member.set({ epilog: ",\n " });
760
772
  }
773
+ /* append a member to the root object, repairing the whitespace at
774
+ the seam: a scalar value carries no pre-"}" newline of its own
775
+ (a container value carries it in its own epilog already), so the
776
+ root epilog has to supply it */
777
+ settingsAddMember(root, member, scalar) {
778
+ const members = root.query("/ member");
779
+ if (members.length === 0)
780
+ root.set({ prolog: "{\n " });
781
+ else
782
+ this.settingsMakeNonLast(members[members.length - 1]);
783
+ root.set({ epilog: scalar ? "\n}\n" : "}\n" });
784
+ root.add(member);
785
+ }
786
+ /* remove a member from the root object, repairing the whitespace at the seam */
787
+ settingsRemoveMember(root, target) {
788
+ const members = root.query("/ member");
789
+ const wasLast = members[members.length - 1] === target;
790
+ root.del(target);
791
+ if (wasLast) {
792
+ if (members.length > 1) {
793
+ /* promote the new last member: strip its comma epilog and
794
+ restore the pre-"}" newline into the root epilog when the
795
+ new-last value is a scalar (a container value carries the
796
+ newline in its own epilog already) */
797
+ const newLast = members[members.length - 2];
798
+ newLast.set({ epilog: undefined });
799
+ const val = newLast.query("/ * [ pos() == 2 ]")[0];
800
+ const ep = val !== undefined ? val.get("epilog") : undefined;
801
+ root.set({ epilog: (typeof ep === "string" && ep.endsWith("\n")) ? "}\n" : "\n}\n" });
802
+ }
803
+ else
804
+ root.set({ epilog: "}\n" });
805
+ }
806
+ }
761
807
  /* read a settings.json file into a JSON-ASTy AST */
762
- async statuslineReadAst(file) {
808
+ async settingsReadAst(file) {
763
809
  let text = "";
764
810
  try {
765
811
  text = await fs.readFile(file, "utf8");
@@ -772,7 +818,7 @@ export default class SetupCommand {
772
818
  return JsonAsty.parse(text);
773
819
  }
774
820
  /* write a JSON-ASTy AST back to a settings.json file */
775
- async statuslineWriteAst(file, root) {
821
+ async settingsWriteAst(file, root) {
776
822
  await mkdirp(path.dirname(file));
777
823
  const text = JsonAsty.unparse(root);
778
824
  await writeFileAtomic(file, text, { encoding: "utf8" });
@@ -781,10 +827,10 @@ export default class SetupCommand {
781
827
  async doStatuslineActivate(tool, scope, opts, format) {
782
828
  this.requireStatuslineTool(tool);
783
829
  this.requireStatuslineScope(tool, scope);
784
- const file = this.statuslineSettingsFile(tool, scope);
830
+ const file = this.settingsFile(tool, scope);
785
831
  const command = this.statuslineCommand(tool, opts, format);
786
- const root = await this.statuslineReadAst(file);
787
- const existing = this.statuslineFindMember(root);
832
+ const root = await this.settingsReadAst(file);
833
+ const existing = this.settingsFindMember(root, "statusLine");
788
834
  if (existing !== undefined) {
789
835
  /* preserve a foreign, hand-crafted statusLine: skip and warn */
790
836
  if (!this.statuslineIsOwned(existing)) {
@@ -801,27 +847,17 @@ export default class SetupCommand {
801
847
  }
802
848
  else {
803
849
  /* insert a fresh statusLine member */
804
- const members = root.query("/ member");
805
- const member = this.statuslineBuildMember(root, command);
806
- if (members.length === 0) {
807
- root.set({ prolog: "{\n ", epilog: "}\n" });
808
- root.add(member);
809
- }
810
- else {
811
- root.set({ epilog: "}\n" });
812
- this.statuslineMakeNonLast(members[members.length - 1]);
813
- root.add(member);
814
- }
850
+ this.settingsAddMember(root, this.statuslineBuildMember(root, command), false);
815
851
  this.log.write("info", `setup: statusline: activate: adding ASE "statusLine" to ${file}`);
816
852
  }
817
- await this.statuslineWriteAst(file, root);
853
+ await this.settingsWriteAst(file, root);
818
854
  return 0;
819
855
  }
820
856
  /* handler for "ase setup statusline deactivate" */
821
857
  async doStatuslineDeactivate(tool, scope) {
822
858
  this.requireStatuslineTool(tool);
823
859
  this.requireStatuslineScope(tool, scope);
824
- const file = this.statuslineSettingsFile(tool, scope);
860
+ const file = this.settingsFile(tool, scope);
825
861
  /* a missing settings file means nothing to remove */
826
862
  try {
827
863
  await fs.access(file);
@@ -831,8 +867,8 @@ export default class SetupCommand {
831
867
  return 0;
832
868
  }
833
869
  /* read file */
834
- const root = await this.statuslineReadAst(file);
835
- const target = this.statuslineFindMember(root);
870
+ const root = await this.settingsReadAst(file);
871
+ const target = this.settingsFindMember(root, "statusLine");
836
872
  if (target === undefined) {
837
873
  this.log.write("info", `setup: statusline: deactivate: no "statusLine" in ${file} (skipped)`);
838
874
  return 0;
@@ -844,26 +880,8 @@ export default class SetupCommand {
844
880
  return 0;
845
881
  }
846
882
  /* remove the member and repair the whitespace at the seam */
847
- const members = root.query("/ member");
848
- const wasLast = members[members.length - 1] === target;
849
- root.del(target);
850
- if (wasLast) {
851
- if (members.length > 1) {
852
- /* promote the new last member: strip its comma epilog and
853
- restore the pre-"}" newline into the root epilog when the
854
- new-last value is a scalar (a container value carries the
855
- newline in its own epilog already) */
856
- const newLast = members[members.length - 2];
857
- newLast.set({ epilog: undefined });
858
- const val = newLast.query("/ * [ pos() == 2 ]")[0];
859
- const ep = val !== undefined ? val.get("epilog") : undefined;
860
- if (!(typeof ep === "string" && ep.endsWith("\n")))
861
- root.set({ epilog: "\n}\n" });
862
- }
863
- else
864
- root.set({ epilog: "}\n" });
865
- }
866
- await this.statuslineWriteAst(file, root);
883
+ this.settingsRemoveMember(root, target);
884
+ await this.settingsWriteAst(file, root);
867
885
  this.log.write("info", `setup: statusline: deactivate: removing ASE "statusLine" from ${file}`);
868
886
  return 0;
869
887
  }
@@ -877,16 +895,16 @@ export default class SetupCommand {
877
895
  const scopes = tool === "claude" ? ["user", "project", "local"] : ["user"];
878
896
  const entries = [];
879
897
  for (const scope of scopes) {
880
- const file = this.statuslineSettingsFile(tool, scope);
898
+ const file = this.settingsFile(tool, scope);
881
899
  let root;
882
900
  try {
883
- root = await this.statuslineReadAst(file);
901
+ root = await this.settingsReadAst(file);
884
902
  }
885
903
  catch {
886
904
  /* an unparsable settings file carries no usable state */
887
905
  continue;
888
906
  }
889
- const member = this.statuslineFindMember(root);
907
+ const member = this.settingsFindMember(root, "statusLine");
890
908
  if (member === undefined)
891
909
  continue;
892
910
  entries.push({
@@ -897,6 +915,90 @@ export default class SetupCommand {
897
915
  }
898
916
  return entries;
899
917
  }
918
+ /* determine whether an existing "outputStyle" value is owned by us */
919
+ outputStyleIsOwned(member) {
920
+ const val = member.query("/ string [ pos() == 2 ]")[0];
921
+ return val !== undefined && val.get("value") === "ase:ase-terse";
922
+ }
923
+ /* build the "outputStyle" member subtree natively inside the target AST */
924
+ outputStyleBuildMember(root) {
925
+ const key = root.create("string").set({ body: JSON.stringify("outputStyle"), value: "outputStyle", epilog: ": " });
926
+ const val = root.create("string").set({ body: JSON.stringify("ase:ase-terse"), value: "ase:ase-terse" });
927
+ return root.create("member").add(key, val);
928
+ }
929
+ /* activate the plugin-shipped ASE output style by selecting it in
930
+ the settings file of the scope (Anthropic Claude Code CLI only:
931
+ the other tools have no output style concept, so the session-start
932
+ hook injects the style into their session context instead) */
933
+ async outputStyleActivate(action, scope) {
934
+ const file = this.settingsFile("claude", scope);
935
+ const root = await this.settingsReadAst(file);
936
+ const existing = this.settingsFindMember(root, "outputStyle");
937
+ if (existing !== undefined) {
938
+ /* preserve a foreign, hand-selected outputStyle: skip and warn */
939
+ if (!this.outputStyleIsOwned(existing))
940
+ this.log.write("warning", `setup: ${action}: a non-ASE "outputStyle" ` +
941
+ `is already selected in ${file}: preserving it (skipped)`);
942
+ return;
943
+ }
944
+ this.settingsAddMember(root, this.outputStyleBuildMember(root), true);
945
+ await this.settingsWriteAst(file, root);
946
+ this.log.write("info", `setup: ${action}: selecting ASE "outputStyle" in ${file}`);
947
+ }
948
+ /* deactivate the plugin-shipped ASE output style by removing its
949
+ selection from the settings file of the scope (Anthropic Claude Code CLI only) */
950
+ async outputStyleDeactivate(action, scope) {
951
+ const file = this.settingsFile("claude", scope);
952
+ /* a missing settings file means nothing to remove */
953
+ try {
954
+ await fs.access(file);
955
+ }
956
+ catch {
957
+ return;
958
+ }
959
+ const root = await this.settingsReadAst(file);
960
+ const target = this.settingsFindMember(root, "outputStyle");
961
+ if (target === undefined)
962
+ return;
963
+ /* preserve a foreign, hand-selected outputStyle: skip and warn */
964
+ if (!this.outputStyleIsOwned(target)) {
965
+ this.log.write("warning", `setup: ${action}: a non-ASE "outputStyle" ` +
966
+ `is selected in ${file}: preserving it (skipped)`);
967
+ return;
968
+ }
969
+ this.settingsRemoveMember(root, target);
970
+ await this.settingsWriteAst(file, root);
971
+ this.log.write("info", `setup: ${action}: deselecting ASE "outputStyle" in ${file}`);
972
+ }
973
+ /* probe the output style selections by inspecting the tool
974
+ settings files of all installation scopes (Anthropic Claude Code CLI only) */
975
+ async outputStyleStatus(tool) {
976
+ if (tool !== "claude")
977
+ return [];
978
+ const home = os.homedir();
979
+ const scopes = ["user", "project", "local"];
980
+ const entries = [];
981
+ for (const scope of scopes) {
982
+ const file = this.settingsFile(tool, scope);
983
+ let root;
984
+ try {
985
+ root = await this.settingsReadAst(file);
986
+ }
987
+ catch {
988
+ /* an unparsable settings file carries no usable state */
989
+ continue;
990
+ }
991
+ const member = this.settingsFindMember(root, "outputStyle");
992
+ if (member === undefined)
993
+ continue;
994
+ entries.push({
995
+ file: file.startsWith(home + path.sep) ? `~${file.slice(home.length)}` : file,
996
+ scope,
997
+ status: this.outputStyleIsOwned(member) ? "selected" : "foreign"
998
+ });
999
+ }
1000
+ return entries;
1001
+ }
900
1002
  /* parse and validate the --tool option */
901
1003
  parseTool(value) {
902
1004
  if (value !== "claude" && value !== "copilot" && value !== "codex")
@@ -985,7 +1087,7 @@ export default class SetupCommand {
985
1087
  /* register CLI sub-command "ase setup status" */
986
1088
  setupCmd
987
1089
  .command("status")
988
- .description("report the ASE plugin, MCP server, and statusline registrations for a tool")
1090
+ .description("report the ASE plugin, MCP server, output style, and statusline registrations for a tool")
989
1091
  .option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
990
1092
  .action(async (opts) => {
991
1093
  process.exit(await this.doStatus(this.parseTool(opts.tool)));
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": "1.0.5",
9
+ "version": "1.0.6",
10
10
  "license": "Apache-2.0",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -55,7 +55,7 @@
55
55
  "luxon": "3.7.2",
56
56
  "@modelcontextprotocol/sdk": "1.30.0",
57
57
  "json-asty": "1.3.4",
58
- "zod": "4.6.4",
58
+ "zod": "4.6.5",
59
59
  "which": "7.0.0",
60
60
  "update-notifier": "7.3.1",
61
61
  "shell-quote": "1.10.0",
@@ -66,7 +66,7 @@
66
66
  "ofetch": "1.5.1",
67
67
  "picomatch": "4.0.7",
68
68
  "lru-cache": "11.5.2",
69
- "@rse/specbook": "1.2.10",
69
+ "@rse/specbook": "1.2.13",
70
70
  "source-code-error": "1.2.0",
71
71
  "pure-uuid": "2.0.0"
72
72
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ase",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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": "1.0.5",
3
+ "version": "1.0.6",
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": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Agentic Software Engineering (ASE)",
5
5
  "keywords": [ "agentic", "software", "engineering" ],
6
6
  "homepage": "https://ase.tools",
@@ -45,7 +45,7 @@
45
45
  optional: true
46
46
  - name: THEME-STYLE
47
47
  desc: The theme style.
48
- value: enum(dark,light)
48
+ value: enum(Dark,Light)
49
49
  optional: true
50
50
  - name: THEME-TONE
51
51
  desc: The theme color tone.
@@ -190,7 +190,7 @@
190
190
  diagram:
191
191
  type: hub
192
192
  center: self
193
- nodes: "[[TERM:*]], [[PREMISE:*]], [[RULE:*]], [[DM.ENTITY:*]], [[ROLE:*]], [[PERSONA:*]]"
193
+ nodes: "[[TERM:*]], [[PREMISE:*]], [[RULE:*]], [[DM.ENTITY:*]], [[ROLE:*]], [[PERSONA:*]], [[WORKFLOW:*]], [[CONVENTION:*]], [[CV.ENTITY:*]]"
194
194
  labeled: true
195
195
  qualified: true
196
196
  config:
@@ -257,7 +257,7 @@
257
257
  diagram:
258
258
  type: hub
259
259
  center: self
260
- nodes: "[[ROLE:*]], [[TERM:*]], [[PERSONA:*]], [[ACTIVITY:*]], [[USE-CASE:*]], [[CV.ENTITY:*]]"
260
+ nodes: "[[ROLE:*]], [[TERM:*]], [[PERSONA:*]], [[WORKFLOW:*]], [[ACTIVITY:*]], [[USE-CASE:*]], [[TRANSITION:*]], [[CV.ENTITY:*]]"
261
261
  labeled: true
262
262
  qualified: true
263
263
  config:
@@ -292,8 +292,8 @@
292
292
  optional: true
293
293
  - name: GENDER
294
294
  desc: >
295
- The gender of the persona: `male`, `female`, or `diverse`.
296
- value: enum(male,female,diverse)
295
+ The gender of the persona: `Male`, `Female`, or `Diverse`.
296
+ value: enum(Male,Female,Diverse)
297
297
  optional: true
298
298
  - name: AGE
299
299
  desc: >
@@ -312,8 +312,8 @@
312
312
  optional: true
313
313
  - name: PROFICIENCY
314
314
  desc: >
315
- The technical and domain skill level of the persona: `novice`, `intermediate`, or `expert`.
316
- value: enum(novice,intermediate,expert)
315
+ The technical and domain skill level of the persona: `Novice`, `Intermediate`, or `Expert`.
316
+ value: enum(Novice,Intermediate,Expert)
317
317
  optional: true
318
318
  - name: GOAL
319
319
  desc: >
@@ -714,7 +714,7 @@
714
714
  diagram:
715
715
  type: hub
716
716
  center: self
717
- nodes: "[[USE-CASE:*]], [[ROLE:*]], [[PERSONA:*]], [[STEP:*]], [[ACTIVITY:*]], [[FR.REQUIREMENT:*]], [[RULE:*]], [[TRANSITION:*]], [[PERMISSION:*]], [[PRINCIPLE:*]], [[CV.ENTITY:*]]"
717
+ nodes: "[[USE-CASE:*]], [[ROLE:*]], [[PERSONA:*]], [[STEP:*]], [[ACTIVITY:*]], [[FR.REQUIREMENT:*]], [[RULE:*]], [[DM.ENTITY:*]], [[TRANSITION:*]], [[PERMISSION:*]], [[PRINCIPLE:*]], [[STORYBOARD:*]], [[CV.ENTITY:*]], [[TEST-CASE:*]]"
718
718
  labeled: true
719
719
  qualified: true
720
720
  config:
@@ -830,7 +830,7 @@
830
830
  diagram:
831
831
  type: hub
832
832
  center: self
833
- nodes: "[[DM.ENTITY:*]], [[FR.REQUIREMENT:*]], [[USE-CASE:*]]"
833
+ nodes: "[[DM.ENTITY:*]], [[FR.REQUIREMENT:*]], [[USE-CASE:*]], [[LIFECYCLE:*]], [[FV.COMPONENT:*]], [[CONCERN:*]], [[TACTIC:*]], [[DECISION:*]], [[TEST-CASE:*]]"
834
834
  edges: "[[RELATION:*]]"
835
835
  labeled: true
836
836
  qualified: true
@@ -894,14 +894,16 @@
894
894
  desc: >
895
895
  A named directed relation of the entity, carrying a camel-cased, 1-3 word long, per-entity unique identifier and targeting another entity, and described by a 1-sentence description ("what") plus a 1-sentence rationale ("why"). A relation carries no attributes of its own: a relation needing attributes (an "association class") is reified as an entity of its own, related from both sides.
896
896
  optional: true
897
+ format:
898
+ maxTableColumns: 5
897
899
  props:
898
900
  - name: TARGET
899
901
  desc: Reference to the DATA-MODEL entity the directed relation targets.
900
902
  value: "[[DM.ENTITY:*]]"
901
903
  - name: KIND
902
904
  desc: >
903
- The kind of the directed relation: `association` for a plain reference to an independent target entity, `aggregation` for a shared part-of relation whose target outlives the entity, or `composition` for an exclusive part-of relation whose target is owned by and deleted with the entity; defaults to `association`.
904
- value: enum(association,aggregation,composition)
905
+ The kind of the directed relation: `Association` for a plain reference to an independent target entity, `Aggregation` for a (optionally even shared) part-of relation whose target outlives the entity, or `Composition` for an exclusive part-of relation whose target is owned by and deleted with the entity; defaults to `Association`.
906
+ value: enum(Association,Aggregation,Composition)
905
907
  optional: true
906
908
  - name: ARITY
907
909
  desc: >
@@ -1109,7 +1111,7 @@
1109
1111
  diagram:
1110
1112
  type: hub
1111
1113
  center: self
1112
- nodes: "[[INTERFACE:*]], [[TYPE:*]], [[ENDPOINT:*]]"
1114
+ nodes: "[[INTERFACE:*]], [[TYPE:*]], [[ENDPOINT:*]], [[FV.COMPONENT:*]]"
1113
1115
  labeled: true
1114
1116
  qualified: true
1115
1117
  config:
@@ -1175,7 +1177,7 @@
1175
1177
  diagram:
1176
1178
  type: hub
1177
1179
  center: self
1178
- nodes: "[[TYPE:*]], [[INTERFACE:*]]"
1180
+ nodes: "[[TYPE:*]], [[INTERFACE:*]], [[ENDPOINT:*]]"
1179
1181
  deep: true
1180
1182
  labeled: true
1181
1183
  qualified: true
@@ -1442,7 +1444,7 @@
1442
1444
  diagram:
1443
1445
  type: hub
1444
1446
  center: self
1445
- nodes: "[[PRINCIPLE:*]], [[PERSONA:*]], [[STEP:*]], [[USE-CASE:*]], [[PATTERN:*]]"
1447
+ nodes: "[[PRINCIPLE:*]], [[PERSONA:*]], [[STEP:*]], [[USE-CASE:*]], [[CONVENTION:*]], [[PATTERN:*]], [[ELEMENT:*]], [[DECISION:*]]"
1446
1448
  labeled: true
1447
1449
  qualified: true
1448
1450
  config:
@@ -1554,7 +1556,7 @@
1554
1556
  diagram:
1555
1557
  type: hub
1556
1558
  center: self
1557
- nodes: "[[PATTERN:*]], [[PRINCIPLE:*]], [[STORYBOARD:*]]"
1559
+ nodes: "[[PATTERN:*]], [[PRINCIPLE:*]], [[STORYBOARD:*]], [[DECISION:*]]"
1558
1560
  deep: true
1559
1561
  labeled: true
1560
1562
  qualified: true
@@ -1620,7 +1622,7 @@
1620
1622
  diagram:
1621
1623
  type: hub
1622
1624
  center: self
1623
- nodes: "[[STORYBOARD:*]], [[PERSONA:*]], [[SCENARIO:*]], [[PATTERN:*]]"
1625
+ nodes: "[[STORYBOARD:*]], [[PERSONA:*]], [[SCENARIO:*]], [[PATTERN:*]], [[MOCKUP:*]]"
1624
1626
  deep: true
1625
1627
  labeled: true
1626
1628
  qualified: true
@@ -1867,7 +1869,7 @@
1867
1869
  diagram:
1868
1870
  type: hub
1869
1871
  center: self
1870
- nodes: "[[FV.COMPONENT:*]], [[FR.REQUIREMENT:*]], [[INTERFACE:*]], [[DM.ENTITY:*]], [[TACTIC:*]], [[DECISION:*]]"
1872
+ nodes: "[[FV.COMPONENT:*]], [[FR.REQUIREMENT:*]], [[INTERFACE:*]], [[DM.ENTITY:*]], [[UNIT:*]], [[NODE:*]], [[CONCERN:*]], [[TACTIC:*]], [[DECISION:*]]"
1871
1873
  labeled: true
1872
1874
  qualified: true
1873
1875
  properties:
@@ -1960,6 +1962,7 @@
1960
1962
  diagram:
1961
1963
  type: hub
1962
1964
  center: self
1965
+ nodes: "[[TS.TIER:*]], [[TS.TIER:*.COMPONENT:*]], [[DV.TIER:*]]"
1963
1966
  hierarchy: true
1964
1967
  properties:
1965
1968
  - PRODUCT
@@ -1975,7 +1978,7 @@
1975
1978
  diagram:
1976
1979
  type: hub
1977
1980
  center: self
1978
- nodes: "[[TS.TIER:*.COMPONENT:*]], [[FV.COMPONENT:*]], [[TACTIC:*]], [[DECISION:*]]"
1981
+ nodes: "[[TS.TIER:*.COMPONENT:*]], [[FV.COMPONENT:*]], [[CONCERN:*]], [[TACTIC:*]], [[DECISION:*]]"
1979
1982
  labeled: true
1980
1983
  qualified: true
1981
1984
  properties:
@@ -2434,7 +2437,7 @@
2434
2437
  diagram:
2435
2438
  type: hub
2436
2439
  center: self
2437
- nodes: "[[DV.TIER:*]], [[AREA:*]], [[NODE:*]], [[NETWORK:*]], [[FV.COMPONENT:*]], [[UNIT:*]], [[TACTIC:*]], [[DECISION:*]]"
2440
+ nodes: "[[DV.TIER:*]], [[AREA:*]], [[NODE:*]], [[NETWORK:*]], [[FV.COMPONENT:*]], [[UNIT:*]], [[CONCERN:*]], [[TACTIC:*]], [[DECISION:*]]"
2438
2441
  labeled: true
2439
2442
  qualified: true
2440
2443
  properties:
@@ -2642,7 +2645,7 @@
2642
2645
  diagram:
2643
2646
  type: hub
2644
2647
  center: self
2645
- nodes: "[[TACTIC:*]], [[NR.REQUIREMENT:*]], [[FV.COMPONENT:*]], [[UNIT:*]], [[ASPECT:*]], [[NODE:*]], [[DM.ENTITY:*]], [[CONCERN:*]], [[DECISION:*]], [[TS.TIER:*.COMPONENT:*]]"
2648
+ nodes: "[[TACTIC:*]], [[NR.REQUIREMENT:*]], [[FV.COMPONENT:*]], [[UNIT:*]], [[ASPECT:*]], [[NODE:*]], [[DM.ENTITY:*]], [[CONCERN:*]], [[DECISION:*]], [[TS.TIER:*.COMPONENT:*]], [[TEST-CASE:*]]"
2646
2649
  labeled: true
2647
2650
  qualified: true
2648
2651
  properties:
@@ -476,6 +476,13 @@ Template Patterns
476
476
  un-styled text <raw-title/>.
477
477
  - Set <bar/> to the `─` character repeated exactly max(0, 67 - <raw-title-len/>)
478
478
  times -- clamped to zero so an over-long title never yields a negative count.
479
+ - Pre-wrap every line of <content/> at *96 visible columns* (the box
480
+ width minus the `│ ` prefix, Markdown markup not counted), breaking
481
+ only at word boundaries and never inside an identifier, path, URL,
482
+ or quoted code: the agent tool wraps longer lines *without* the `│ `
483
+ prefix and visibly breaks the box. Indent each continuation line to
484
+ the text column of its first line (e.g. behind a key glyph, bullet,
485
+ or padded label), so gutters stay aligned.
479
486
  - Set <body/> to <content/> with all non-empty lines prefixed with
480
487
  `│ ` and all empty lines prefixed with just `│` (so no trailing
481
488
  whitespace is produced). Do *not* prefix any line twice -- the
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: "ase-terse"
3
+ description: Respond tersely, leading with results and skipping preamble and narration.
4
+ keep-coding-instructions: true
5
+ force-for-plugin: false
6
+ ---
7
+
8
+ ## ASE Output Style
9
+
10
+ In your communication to the user you should:
11
+
12
+ - **Assume no shared context**:
13
+ The user has not read anything you read: no files, no tool output, no
14
+ search results. Only your messages. Assume the user has no prior context on
15
+ internal files or draft iterations.
16
+
17
+ - **Respond tersely**:
18
+ The user chose brevity over narration, so keep your responses short and
19
+ direct while doing the work just as thoroughly.
20
+
21
+ - **Lead with the result**:
22
+ Your **FIRST** sentence **MUST** state the direct answer, verdict, or status
23
+ immediately and with zero preamble (like "Let me...", "Now I'll...", etc.).
24
+
25
+ - **Cut narration, keep substance**:
26
+ Don't restate the request, the plan, or each step you took. Report outcomes,
27
+ decisions, and anything the user must act on.
28
+
29
+ - **Select formatting per output block**:
30
+ Map decisions/tradeoffs to a *table*, temporal steps to a *numbered
31
+ list*, concepts to *bulleted list* anchors, caveats/warnings/notices to a
32
+ *callout/quote* (`> **Notice:**`), previews of code edits to a *unified
33
+ diff*, and execution syntax to a *code block*.
34
+
35
+ - **Execute tools silently**:
36
+ Perform intermediate tool operations silently without narrating your intent
37
+ or planned tool calls in your messages.
38
+
39
+ - **State things plainly**:
40
+ Skip hedging boilerplate. Mention a caveat only when it changes what the
41
+ user should do next.
42
+
43
+ - **Give full detail on request**:
44
+ When the user asks for an explanation or detail, answer completely.
45
+ Conciseness never means withholding requested information.
46
+
47
+ - **Never trade correctness for brevity**:
48
+ Error reports, failing test output, security warnings, and confirmations for
49
+ destructive actions keep their full content.
50
+
51
+ - **End without closing recap**:
52
+ Avoid telling the user what you already said in the same turn.
53
+
54
+ Where these rules conflict with the default communication and formatting
55
+ guidance of the agent harness, these rules here win. But these rules never
56
+ override the ASE persona communication style, which stays in effect at all
57
+ times.
58
+