@rse/ase 2.0.5 → 2.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.
Files changed (38) hide show
  1. package/dst/ase-config-schema.js +1 -1
  2. package/dst/ase-setup-settings.js +111 -1
  3. package/dst/ase-setup.js +19 -7
  4. package/dst/ase-spec.js +184 -49
  5. package/dst/ase-task-board-tui-model.js +8 -1
  6. package/dst/ase-task-board-tui.js +26 -2
  7. package/dst/ase-task-board-web-client.js +10 -10
  8. package/dst/man/ase-artifact.md +1 -1
  9. package/dst/man/ase-config.md +5 -3
  10. package/dst/man/ase-mcp.md +1 -1
  11. package/dst/man/ase-setup-install.md +10 -1
  12. package/dst/man/ase-setup-status.md +6 -0
  13. package/dst/man/ase-setup-uninstall.md +3 -1
  14. package/dst/man/ase-setup-update.md +3 -0
  15. package/dst/man/ase-setup.md +4 -4
  16. package/dst/man/ase-spec-export.md +14 -3
  17. package/dst/man/ase-spec-init.md +10 -1
  18. package/dst/man/ase-spec-lint.md +2 -1
  19. package/dst/man/ase-spec-preview.md +10 -1
  20. package/dst/man/ase-spec-query.md +96 -0
  21. package/dst/man/ase-spec.md +32 -11
  22. package/dst/man/ase.md +3 -3
  23. package/package.json +18 -15
  24. package/plugin/.claude-plugin/icon.png +0 -0
  25. package/plugin/.claude-plugin/plugin.json +1 -1
  26. package/plugin/.codex-plugin/plugin.json +1 -1
  27. package/plugin/.github/plugin/plugin.json +1 -1
  28. package/plugin/meta/ase-format-spec.md +29 -5
  29. package/plugin/meta/ase-format-specbook.md +427 -47
  30. package/plugin/meta/ase-format-specbook.yaml +180 -78
  31. package/plugin/mods/ase-mods-common.ts +36 -6
  32. package/plugin/mods/ase-mods-evaluate.ts +227 -0
  33. package/plugin/mods/ase-mods-grill.ts +1 -30
  34. package/plugin/mods/ase-mods-help-browse.ts +18 -13
  35. package/plugin/mods/ase-mods-help-find.ts +17 -9
  36. package/plugin/mods/ase-mods.ts +23 -17
  37. package/plugin/skills/ase-spec-activate/SKILL.md +4 -1
  38. package/plugin/mods/claude-code.d.ts +0 -13440
@@ -789,6 +789,9 @@
789
789
  desc: >
790
790
  The access rights of the solution, granted to the roles of the User Personas and stating for each role the operations it may perform on each entity, confined to the lifecycle states of the entity and to a further condition where necessary, so that "who may do what on which entity in which state" is answered in one place instead of being scattered over the actors of the state transitions, the actors of the use cases, and the prose of the domain rules. The model is closed: an operation not granted to a role is forbidden, exactly like a move not listed as a state transition.
791
791
  optional: true
792
+ coverage:
793
+ - "[[DM.ENTITY:*]]"
794
+ - "[[USE-CASE:*]]"
792
795
  children:
793
796
  - kind: PERMISSION
794
797
  desc: >
@@ -1194,6 +1197,8 @@
1194
1197
  desc: >
1195
1198
  The sequenced visual or textual depiction of a specific dialog flow, illustrating how an interaction unfolds screen by screen or turn by turn.
1196
1199
  optional: true
1200
+ coverage:
1201
+ - "[[USE-CASE:*]]"
1197
1202
  children:
1198
1203
  - kind: STORYBOARD
1199
1204
  desc: >
@@ -1433,6 +1438,9 @@
1433
1438
  desc: >
1434
1439
  The functional components of the solution, their responsibilities, interfaces, and primary interactions, describing *how* the solution's runtime elements are structured and which responsibility each owns, delivering the behaviors the Functional Requirements state.
1435
1440
  optional: true
1441
+ coverage:
1442
+ - "[[FR.REQUIREMENT:*]]"
1443
+ - "[[DM.ENTITY:*]]"
1436
1444
  children:
1437
1445
  - kind: COMPONENT
1438
1446
  desc: >
@@ -1473,7 +1481,7 @@
1473
1481
  optional: true
1474
1482
  - name: PROVIDES
1475
1483
  desc: >
1476
- Comma-separated references to the INTERFACE-MODEL objects the component provides, i.e. through which other components and external entities interact with it; omitted when the component provides no modeled interface (e.g. a user interface or an internal module).
1484
+ Comma-separated references to the INTERFACE-MODEL objects the component provides, i.e. through which other components and external entities interact with it, which are always `Provided` ones, as a `Required` interface is offered by an external entity instead; omitted when the component provides no modeled interface (e.g. a user interface or an internal module).
1477
1485
  value: list([[INTERFACE:*]])
1478
1486
  optional: true
1479
1487
  - name: CONSUMES
@@ -1487,10 +1495,150 @@
1487
1495
  value: list([[FV.COMPONENT:*]])
1488
1496
  optional: true
1489
1497
 
1498
+ - kind: ARCH
1499
+ name: Runtime View
1500
+ id: RV
1501
+ file: 26-ARCH-RV-Runtime-View.md
1502
+ desc: >
1503
+ The runtime interactions of the solution: for each significant use case scenario or technical situation, the ordered messages its participants -- roles, Functionality View components, and Context View entities -- exchange, describing *how* the components collaborate at runtime to deliver the behaviors their static structure merely enables.
1504
+ optional: true
1505
+ coverage:
1506
+ - "[[USE-CASE:*]]"
1507
+ - "[[SCENARIO:*]]"
1508
+ children:
1509
+ - kind: INTERACTION
1510
+ desc: >
1511
+ A single runtime interaction, named with a short (2-5 word) summary of the situation it shows (e.g. `Ask a Question`, `Login with Token`) and described in a concise (1-3 sentence) paragraph by its trigger and its outcome, plus a 1-sentence rationale ("why") stating what the interaction makes explicit, while its MESSAGE children, in document order, are the messages its participants exchange.
1512
+ optional: true
1513
+ props:
1514
+ - name: REALIZES
1515
+ desc: >
1516
+ Comma-separated references to the USE-CASES objects (use cases or their scenarios) the interaction realizes, tracing the runtime behavior back to the behavior the requirements state; omitted for a purely technical interaction (e.g. a startup or a failover).
1517
+ value: list([[USE-CASE:*]], [[SCENARIO:*]])
1518
+ optional: true
1519
+ children:
1520
+ - kind: MESSAGE
1521
+ desc: >
1522
+ A single message of the interaction, named with a short (1-4 word), per-interaction unique summary of the operation, event, or response it carries (e.g. `submit question`, `question stored`) and described by a 1-sentence description of its effect.
1523
+ props:
1524
+ - name: FROM
1525
+ desc: >
1526
+ Reference to the participant sending the message: a USER-PERSONAS role, a FUNCTIONALITY-VIEW component, or a CONTEXT-VIEW entity.
1527
+ value: list([[ROLE:*]], [[FV.COMPONENT:*]], [[CV.ENTITY:*]])
1528
+ - name: TO
1529
+ desc: >
1530
+ Comma-separated references to the participants receiving the message (several ones yielding one message each): USER-PERSONAS roles, FUNCTIONALITY-VIEW components, or CONTEXT-VIEW entities.
1531
+ value: list([[ROLE:*]], [[FV.COMPONENT:*]], [[CV.ENTITY:*]])
1532
+ - name: KIND
1533
+ desc: |
1534
+ The kind of the message, one of:
1535
+ - `Call`: Synchronous request the sender waits for (the default).
1536
+ - `Signal`: Asynchronous notification the sender does not wait for.
1537
+ - `Return`: Response to the latest unanswered call of its receiver.
1538
+ - `Create`: Instantiation of its receiver, which starts there.
1539
+ - `Destroy`: Termination of its receiver, which ends there.
1540
+ value: enum(Call,Signal,Return,Create,Destroy)
1541
+ optional: true
1542
+ - name: DURATION
1543
+ desc: >
1544
+ Duration constraint of a call up to its return (e.g. `< 200ms`), drawn as a bracket beside the lifeline of the caller.
1545
+ optional: true
1546
+ - name: TIME
1547
+ desc: >
1548
+ Time constraint or observation at the start of the message (e.g. `t = now`), drawn beside the lifeline of the sender.
1549
+ optional: true
1550
+
1551
+ - kind: ARCH
1552
+ name: Concurrency View
1553
+ id: CO
1554
+ file: 27-ARCH-CO-Concurrency-View.md
1555
+ desc: >
1556
+ The concurrency structure of the solution, mapping functional components to processes and threads, and defining how they coordinate and communicate. The artifact is absent when the solution runs as a single sequential unit of execution and has no concurrency structure worth describing.
1557
+ optional: true
1558
+ coverage:
1559
+ - "[[FV.COMPONENT:*]]"
1560
+ children:
1561
+ - kind: RESOURCE
1562
+ desc: >
1563
+ A single coordination resource the concurrency units share, named with a short (2-5 word) summary (e.g. `Ingest Queue`, `Database Connections`, `Render Lock`) and described in a concise (1-3 sentence) paragraph by what its items are and how full it may get, plus a 1-sentence rationale ("why") for its bound.
1564
+
1565
+ The resources and the units form a Petri net: a resource is a place holding items (tokens), a unit a transition taking items out of the resources it names in `CONSUMES` and putting items into the resources it names in `PRODUCES`, so the analysis of the net reveals unbounded resources (missing backpressure), deadlocks, and units never or no longer able to work.
1566
+ optional: true
1567
+ props:
1568
+ - name: KIND
1569
+ desc: |
1570
+ The kind of the coordination resource, one of:
1571
+
1572
+ - `Queue`: A buffer of work items between producers and consumers.
1573
+ - `Topic`: A publish/subscribe channel delivering messages.
1574
+ - `Pool`: A bounded set of reusable instances (e.g. connections).
1575
+ - `Mutex`: An exclusive lock (a single token).
1576
+ - `Semaphore`: A counting lock (several tokens).
1577
+ value: enum(Queue,Topic,Pool,Mutex,Semaphore)
1578
+ - name: CAPACITY
1579
+ desc: >
1580
+ The maximum number of items the resource holds (e.g. a queue limit or the size of a pool), omitted for an unbounded resource.
1581
+ value: /^[1-9][0-9]*$/
1582
+ optional: true
1583
+ - name: TOKENS
1584
+ desc: >
1585
+ The number of items the resource initially holds (e.g. the free connections of a pool or the `1` of a free mutex), omitted for none.
1586
+ value: /^(?:0|[1-9][0-9]*)$/
1587
+ optional: true
1588
+ - kind: UNIT
1589
+ desc: >
1590
+ A single concurrency unit, named with a short (2-5 word) summary (e.g. `Web Worker Pool`, `Background Scheduler`, `Ingest Queue Consumer`) and described in a concise (1-3 sentence) paragraph by how it executes and how it avoids or manages contention, races, and deadlocks (including its start order and failure/restart behavior where non-obvious), plus a 1-sentence rationale ("why") for the chosen concurrency structure.
1591
+
1592
+ A unit executes work at runtime: a queue or channel between units is not a unit but the coordination mechanism stated in `COORDINATION`, its contract an Interface Model interface (e.g. of kind `MQTT` or `WebSocket`), and the platform element hosting a managed broker a Deployment View `Managed` node -- only the executing structure itself belongs to the Concurrency View.
1593
+ optional: true
1594
+ props:
1595
+ - name: KIND
1596
+ desc: |
1597
+ The kind of the concurrency unit, one of:
1598
+
1599
+ - `Process`: An independent OS process with its own address space.
1600
+ - `Thread`: A thread of execution within a process.
1601
+ - `Task`: A lightweight cooperative task (e.g. coroutine, fiber).
1602
+ - `Pool`: A managed group of interchangeable workers.
1603
+ - `EventLoop`: A single-threaded cooperative scheduler of tasks.
1604
+ value: enum(Process,Thread,Task,Pool,EventLoop)
1605
+ - name: PART-OF
1606
+ desc: >
1607
+ Reference to the CONCURRENCY-VIEW object this unit is contained in, expressing the containment the kinds imply: a `Thread`, `Task`, `Pool`, or `EventLoop` references the `Process` it executes within, and a `Task` the `EventLoop` or `Pool` scheduling it; omitted for a top-level unit.
1608
+ value: "[[UNIT:*]]"
1609
+ optional: true
1610
+ acyclic: true
1611
+ - name: HOSTS
1612
+ desc: >
1613
+ Comma-separated references to the FUNCTIONALITY-VIEW objects that run inside this concurrency unit; omitted when no modeled component runs inside it.
1614
+ value: list([[FV.COMPONENT:*]])
1615
+ optional: true
1616
+ - name: MULTIPLICITY
1617
+ desc: >
1618
+ How many instances of the unit run (e.g. `1`, `1 per CPU core`, `0..n auto-scaled`).
1619
+ - name: COORDINATES-WITH
1620
+ desc: >
1621
+ Comma-separated references to the CONCURRENCY-VIEW objects this unit coordinates or communicates with at runtime, naming the peer unit only (e.g. a producer names the consumer it feeds through a shared queue), while the mechanism is stated by `COORDINATION`.
1622
+ value: list([[UNIT:*]])
1623
+ optional: true
1624
+ - name: COORDINATION
1625
+ desc: >
1626
+ The mechanism by which the unit coordinates or communicates with other units, usually those referenced in `COORDINATES-WITH` (e.g. `shared queue`, `mutex`, `message passing`, `optimistic locking`).
1627
+ - name: CONSUMES
1628
+ desc: >
1629
+ Comma-separated references to the CONCURRENCY-VIEW resources the unit takes work items or instances out of (e.g. the queue it drains, the pool it takes a connection from), modeling the unit as a transition of the Petri net its resources form; omitted when it consumes from none.
1630
+ value: list([[RESOURCE:*]])
1631
+ optional: true
1632
+ - name: PRODUCES
1633
+ desc: >
1634
+ Comma-separated references to the CONCURRENCY-VIEW resources the unit puts work items or freed instances into (e.g. the queue it feeds, the pool it returns a connection to); omitted when it produces into none.
1635
+ value: list([[RESOURCE:*]])
1636
+ optional: true
1637
+
1490
1638
  - kind: ARCH
1491
1639
  name: Technology Stack
1492
1640
  id: TS
1493
- file: 26-ARCH-TS-Technology-Stack.md
1641
+ file: 28-ARCH-TS-Technology-Stack.md
1494
1642
  desc: >
1495
1643
  The concrete technology products the solution is built upon, organized by tier and lifecycle phase, recording for each component its chosen product, its purpose, and the alternatives that were considered but not chosen.
1496
1644
  optional: true
@@ -1764,7 +1912,7 @@
1764
1912
  - kind: ARCH
1765
1913
  name: Development Aspects
1766
1914
  id: DA
1767
- file: 27-ARCH-DA-Development-Aspects.md
1915
+ file: 29-ARCH-DA-Development-Aspects.md
1768
1916
  desc: >
1769
1917
  The architecture supporting the software development process, defining the module organization, build approach, and codeline conventions. The artifact is absent when the development conventions are governed outside the specification (e.g. by repository-level contributor instructions).
1770
1918
  optional: true
@@ -1786,61 +1934,15 @@
1786
1934
  value: list([[MAXIM:*]], [[AT.PRINCIPLE:*]])
1787
1935
  optional: true
1788
1936
 
1789
- - kind: ARCH
1790
- name: Concurrency View
1791
- id: CO
1792
- file: 28-ARCH-CO-Concurrency-View.md
1793
- desc: >
1794
- The concurrency structure of the solution, mapping functional components to processes and threads, and defining how they coordinate and communicate. The artifact is absent when the solution runs as a single sequential unit of execution and has no concurrency structure worth describing.
1795
- optional: true
1796
- children:
1797
- - kind: UNIT
1798
- desc: >
1799
- A single concurrency unit, named with a short (2-5 word) summary (e.g. `Web Worker Pool`, `Background Scheduler`, `Ingest Queue Consumer`) and described in a concise (1-3 sentence) paragraph by how it executes and how it avoids or manages contention, races, and deadlocks (including its start order and failure/restart behavior where non-obvious), plus a 1-sentence rationale ("why") for the chosen concurrency structure.
1800
-
1801
- A unit executes work at runtime: a queue or channel between units is not a unit but the coordination mechanism stated in `COORDINATION`, its contract an Interface Model interface (e.g. of kind `MQTT` or `WebSocket`), and the platform element hosting a managed broker a Deployment View `Managed` node -- only the executing structure itself belongs to the Concurrency View.
1802
- optional: true
1803
- props:
1804
- - name: KIND
1805
- desc: |
1806
- The kind of the concurrency unit, one of:
1807
-
1808
- - `Process`: An independent OS process with its own address space.
1809
- - `Thread`: A thread of execution within a process.
1810
- - `Task`: A lightweight cooperative task (e.g. coroutine, fiber).
1811
- - `Pool`: A managed group of interchangeable workers.
1812
- - `EventLoop`: A single-threaded cooperative scheduler of tasks.
1813
- value: enum(Process,Thread,Task,Pool,EventLoop)
1814
- - name: PART-OF
1815
- desc: >
1816
- Reference to the CONCURRENCY-VIEW object this unit is contained in, expressing the containment the kinds imply: a `Thread`, `Task`, `Pool`, or `EventLoop` references the `Process` it executes within, and a `Task` the `EventLoop` or `Pool` scheduling it; omitted for a top-level unit.
1817
- value: "[[UNIT:*]]"
1818
- optional: true
1819
- acyclic: true
1820
- - name: HOSTS
1821
- desc: >
1822
- Comma-separated references to the FUNCTIONALITY-VIEW objects that run inside this concurrency unit; omitted when no modeled component runs inside it.
1823
- value: list([[FV.COMPONENT:*]])
1824
- optional: true
1825
- - name: MULTIPLICITY
1826
- desc: >
1827
- How many instances of the unit run (e.g. `1`, `1 per CPU core`, `0..n auto-scaled`).
1828
- - name: COORDINATES-WITH
1829
- desc: >
1830
- Comma-separated references to the CONCURRENCY-VIEW objects this unit coordinates or communicates with at runtime, naming the peer unit only (e.g. a producer names the consumer it feeds through a shared queue), while the mechanism is stated by `COORDINATION`.
1831
- value: list([[UNIT:*]])
1832
- optional: true
1833
- - name: COORDINATION
1834
- desc: >
1835
- The mechanism by which the unit coordinates or communicates with other units, usually those referenced in `COORDINATES-WITH` (e.g. `shared queue`, `mutex`, `message passing`, `optimistic locking`).
1836
-
1837
1937
  - kind: ARCH
1838
1938
  name: Deployment View
1839
1939
  id: DV
1840
- file: 29-ARCH-DV-Deployment-View.md
1940
+ file: 30-ARCH-DV-Deployment-View.md
1841
1941
  desc: >
1842
1942
  The environment into which the solution is deployed, partitioning it into tiers and mapping software elements to the runtime platform, hardware, and network topology.
1843
1943
  optional: true
1944
+ coverage:
1945
+ - "[[UNIT:*]]"
1844
1946
  children:
1845
1947
  - kind: TIER
1846
1948
  desc: >
@@ -1870,6 +1972,27 @@
1870
1972
 
1871
1973
  An area is a logically distinct, domain-induced partition of the solution deployed as physical units of its own, the *vertical* split when drawn (a `TIER` is the *horizontal*, technology-induced one): a node dedicated to the area references it via its `AREA`, a node shared by several areas references none, and a domain grouping without deployment units of its own is a Functionality View `Subsystem` instead. The kind is absent when the solution is not split vertically.
1872
1974
  optional: true
1975
+ - kind: NETWORK
1976
+ desc: >
1977
+ A single network segment the deployment nodes are attached to, named with a short (2-5 word) summary (e.g. `Public Internet`, `Backend VLAN`, `Database Subnet`) and described in a concise (1-3 sentence) paragraph by its reach, its isolation from the other segments, and the traffic it carries, plus a 1-sentence rationale ("why") for the segmentation.
1978
+
1979
+ A segment is the medium the nodes communicate over: the attached nodes reference it via `NETWORKS`, the directed links between them are their `CONNECTS-TO` references, and the contract of the carried traffic is an Interface Model interface -- only the network itself belongs here. The kind is absent when the topology has no segmentation worth describing.
1980
+ optional: true
1981
+ props:
1982
+ - name: SCOPE
1983
+ desc: |
1984
+ The reach of the segment, one of:
1985
+ - `Public`: Reachable from the Internet.
1986
+ - `Private`: Reachable from the solution's own nodes only.
1987
+ - `Isolated`: Reachable from selected nodes only (e.g. a database subnet).
1988
+ value: enum(Public,Private,Isolated)
1989
+ - name: PROTOCOLS
1990
+ desc: >
1991
+ The protocols and ports carried by the segment (e.g. `HTTPS 443, WSS 443`, `HTTP, WS, TCP 5432`).
1992
+ - name: ADDRESSING
1993
+ desc: >
1994
+ The address range or naming of the segment (e.g. `10.0.0.0/8`, `public IPv4 and IPv6`, `*.example.com`).
1995
+ optional: true
1873
1996
  - kind: NODE
1874
1997
  desc: >
1875
1998
  A single deployment node, named with a short (2-5 word) summary (e.g. `Web Servers`, `Database Cluster`, `Edge CDN`) and described in a concise (1-3 sentence) paragraph by the node, its sizing, and its place in the deployment topology, plus a 1-sentence rationale ("why") for the node's placement and platform choice.
@@ -1925,32 +2048,11 @@
1925
2048
  Comma-separated references to the DEPLOYMENT-VIEW network segments the node is attached to (e.g. a public ingress and a private backend network); omitted when the topology models no network segments.
1926
2049
  value: list([[NETWORK:*]])
1927
2050
  optional: true
1928
- - kind: NETWORK
1929
- desc: >
1930
- A single network segment the deployment nodes are attached to, named with a short (2-5 word) summary (e.g. `Public Internet`, `Backend VLAN`, `Database Subnet`) and described in a concise (1-3 sentence) paragraph by its reach, its isolation from the other segments, and the traffic it carries, plus a 1-sentence rationale ("why") for the segmentation.
1931
-
1932
- A segment is the medium the nodes communicate over: the attached nodes reference it via `NETWORKS`, the directed links between them are their `CONNECTS-TO` references, and the contract of the carried traffic is an Interface Model interface -- only the network itself belongs here. The kind is absent when the topology has no segmentation worth describing.
1933
- optional: true
1934
- props:
1935
- - name: SCOPE
1936
- desc: |
1937
- The reach of the segment, one of:
1938
- - `Public`: Reachable from the Internet.
1939
- - `Private`: Reachable from the solution's own nodes only.
1940
- - `Isolated`: Reachable from selected nodes only (e.g. a database subnet).
1941
- value: enum(Public,Private,Isolated)
1942
- - name: PROTOCOLS
1943
- desc: >
1944
- The protocols and ports carried by the segment (e.g. `HTTPS 443, WSS 443`, `HTTP, WS, TCP 5432`).
1945
- - name: ADDRESSING
1946
- desc: >
1947
- The address range or naming of the segment (e.g. `10.0.0.0/8`, `public IPv4 and IPv6`, `*.example.com`).
1948
- optional: true
1949
2051
 
1950
2052
  - kind: ARCH
1951
2053
  name: Operations View
1952
2054
  id: OV
1953
- file: 30-ARCH-OV-Operations-View.md
2055
+ file: 31-ARCH-OV-Operations-View.md
1954
2056
  desc: >
1955
2057
  The way the solution is operated, administered, and supported in production, defining the standing procedures by which the operating organization installs, configures, migrates, observes, protects, upgrades, scales, and supports the running solution.
1956
2058
  optional: true
@@ -2012,7 +2114,7 @@
2012
2114
  - kind: ARCH
2013
2115
  name: Quality Perspectives
2014
2116
  id: QP
2015
- file: 31-ARCH-QP-Quality-Perspectives.md
2117
+ file: 32-ARCH-QP-Quality-Perspectives.md
2016
2118
  desc: >
2017
2119
  How the Non-Functional Requirements (NR) are addressed by the architecture, through the tactics built into the solution across the views.
2018
2120
  optional: true
@@ -2070,7 +2172,7 @@
2070
2172
  - kind: ARCH
2071
2173
  name: Design Decisions
2072
2174
  id: DD
2073
- file: 32-ARCH-DD-Design-Decisions.md
2175
+ file: 33-ARCH-DD-Design-Decisions.md
2074
2176
  desc: >
2075
2177
  The major decisions related to the solution design, each recording the forces at play, the chosen response, and the reasoning that justifies it. The artifact is the last decision-level one of the architecture, as a decision references the requirements forcing it, the elements it shapes, and the tactics and products it chose, all of which precede it, while the Code Patterns following it reference the decisions they implement.
2076
2178
  optional: true
@@ -2257,7 +2359,7 @@
2257
2359
  - kind: ARCH
2258
2360
  name: Code Patterns
2259
2361
  id: CP
2260
- file: 33-ARCH-CP-Code-Patterns.md
2362
+ file: 34-ARCH-CP-Code-Patterns.md
2261
2363
  desc: >
2262
2364
  The implementation patterns the source code of the solution follows, each prescribing how one recurring concern (e.g. authorization enforcement, entity persistence, error propagation) is coded and showing it as a code listing. The artifact follows the Design Decisions, as a pattern is the coded form of the tactics and decisions preceding it, and is absent when the coding conventions are governed outside the specification (e.g. by repository-level contributor instructions).
2263
2365
  optional: true
@@ -2304,7 +2406,7 @@
2304
2406
  - kind: TEST
2305
2407
  name: Test Cases
2306
2408
  id: TC
2307
- file: 34-TEST-TC-Test-Cases.md
2409
+ file: 35-TEST-TC-Test-Cases.md
2308
2410
  desc: >
2309
2411
  The verifiable conditions and steps used to confirm that requirements, domain rules, use case scenarios, state transitions, and the mechanisms, procedures, and decisions of the architecture are correctly implemented, with mandatory defined inputs, mandatory expected outcomes, and optional pre- and post-conditions. The artifact is the last one, as a test case verifies objects of every preceding kind.
2310
2412
 
@@ -5,11 +5,12 @@
5
5
  */
6
6
 
7
7
  /* Claude Code function hooks module ("mod", EARLY ACCESS):
8
- Common parts, shared by the grill table, the intent help, the synopsis
9
- form, the manual band, the manpage browser, and the hint band: the styled
10
- characters of the terminal drawing, their Markdown conversion, wrapping,
11
- and framing, and the contract, keys, geometry, and drawing of the
12
- master/detail view in the band above the prompt. */
8
+ Common parts, shared by the grill table, the decision matrix, the intent
9
+ help, the synopsis form, the manual band, the manpage browser, and the hint
10
+ band: the rows of the Markdown tables, the styled characters of the terminal
11
+ drawing, their Markdown conversion, wrapping, and framing, and the contract,
12
+ keys, geometry, and drawing of the master/detail view in the band above the
13
+ prompt. */
13
14
 
14
15
  import type { Elements, Frozen, MatchedEvent, ModelCompleteRequest, ModelForkResult,
15
16
  PromptBox, PromptEditInput, PromptFilled, RenderElement } from "claude-code"
@@ -100,6 +101,35 @@ export const decode = (e: Frozen<PromptEditInput>): Key => {
100
101
  }
101
102
  }
102
103
 
104
+ /* a row and the delimiter row of a Markdown table */
105
+ export const ROW = /^ *\|.*\| *$/
106
+ export const RULE = /^ *\|(?: *:?-+:? *\|)+ *$/
107
+
108
+ /* split a Markdown table row into its cells */
109
+ export const cells = (line: string): string[] => {
110
+ const result: string[] = []
111
+ const body = line.trim().replace(/^\|/, "").replace(/\|$/, "")
112
+ let cell = ""
113
+ let code = false
114
+ for (let i = 0; i < body.length; i++) {
115
+ const ch = body[i]!
116
+ if (ch === "`")
117
+ code = !code
118
+ if (!code && ch === "\\" && body[i + 1] === "|") {
119
+ cell += "|"
120
+ i++
121
+ }
122
+ else if (!code && ch === "|") {
123
+ result.push(cell.trim())
124
+ cell = ""
125
+ }
126
+ else
127
+ cell += ch
128
+ }
129
+ result.push(cell.trim())
130
+ return result
131
+ }
132
+
103
133
  /* a single styled character of the terminal drawing */
104
134
  export type Char = { ch: string, bold: boolean, color: string | undefined }
105
135
 
@@ -214,7 +244,7 @@ export const uniform = (text: string, bold: boolean, color: string | undefined):
214
244
  Array.from(text, (ch) => ({ ch, bold, color }))
215
245
 
216
246
  /* strip the single-asterisk emphasis outside the code spans of inline Markdown */
217
- const strip = (text: string): string =>
247
+ export const strip = (text: string): string =>
218
248
  text.split("`").map((part, i) => i % 2 === 0 ? part.replace(/(?<!\*)\*(?!\*)/g, "") : part).join("`")
219
249
 
220
250
  /* convert the Markdown blocks of a manual page section (paragraphs, list items,
@@ -0,0 +1,227 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
+ */
6
+
7
+ /* Claude Code function hooks module ("mod", EARLY ACCESS):
8
+ Decision matrix, redrawing the weighted decision matrices of the
9
+ "ase-meta-evaluate" skill as a grid and rendering in blue and bold the
10
+ column of the best alternative (resp. of all tied best ones). Its
11
+ header cells are clickable and sort the criteria rows by their column
12
+ (cycling the primary, the reverse, and the original order), while the
13
+ rating row stays the last one. */
14
+
15
+ import type { Elements, Frozen, MatchedEvent, RenderElement } from "claude-code"
16
+ import { type Engine, type Char, ROW, RULE, cells, strip, style, width, wrap, split, paint } from "./ase-mods-common.ts"
17
+
18
+ /* the marker line above a decision matrix */
19
+ const MARKER = /\*{0,2}EVALUATION\*{0,2}:\s+\*{0,2}Weighted Multi-Criteria Decision Matrix/
20
+
21
+ /* the color of the best alternatives, and the rating distance (relative to
22
+ the best rating) below which alternatives tie (as in "ase-meta-evaluate") */
23
+ const WINNER = "blue"
24
+ const TIE = 0.01
25
+
26
+ /* the sort marks of the orders (each taking the same columns) */
27
+ const MARK = { desc: " ▼", asc: " ▲", none: " " } as const
28
+
29
+ /* the indent of the wrapped lines of a criterion, the narrowest criteria
30
+ column, and the size limit of a single "Markdown" element */
31
+ const INDENT = 2
32
+ const NARROW = 12
33
+ const LIMIT = 10000
34
+
35
+ /* a decision matrix with the Markdown text before and after it: its header
36
+ cells, its criteria rows (with the values of their weight and evaluations),
37
+ its rating row, and the columns of the best alternatives */
38
+ type Matrix = {
39
+ before: string,
40
+ after: string,
41
+ header: string[],
42
+ rows: { cells: string[], values: number[] }[],
43
+ rating: string[],
44
+ winners: Set<number>
45
+ }
46
+
47
+ /* the sort of a matrix: the sorted column and its order */
48
+ type Order = "desc" | "asc"
49
+ type Sort = { column: number, order: Order }
50
+
51
+ /* read the number of a cell (without bold markup and a leading plus sign) */
52
+ const number = (cell: string): number => {
53
+ const text = cell.replace(/\*/g, "").trim().replace(/^\+/, "")
54
+ return text !== "" ? Number(text) : NaN
55
+ }
56
+
57
+ /* parse the decision matrix out of the text of a reply */
58
+ const parse = (text: string): Matrix | undefined => {
59
+ const lines = text.split("\n")
60
+ const m = lines.findIndex((line) => MARKER.test(line))
61
+ const h = lines.findIndex((line, i) => m !== -1 && i > m && ROW.test(line))
62
+ if (h === -1 || !RULE.test(lines[h + 1] ?? ""))
63
+ return undefined
64
+ const header = cells(lines[h]!).map((cell) => cell.replace(/\*/g, ""))
65
+ if (header.length < 3 || !/Criteria/.test(header[0]!) || !/Weight/.test(header[1]!))
66
+ return undefined
67
+ let end = h + 2
68
+ while (end < lines.length && ROW.test(lines[end]!))
69
+ end++
70
+ const rows = lines.slice(h + 2, end).map((line) => cells(line))
71
+ const rating = rows.pop() ?? []
72
+ if (rows.length === 0 || rows.some((row) => row.length !== header.length)
73
+ || rating.length !== header.length || rating[0]!.replace(/\*/g, "") !== "RATING")
74
+ return undefined
75
+ const values = rows.map((row) => row.slice(1).map(number))
76
+ const ratings = rating.slice(2).map(number)
77
+ if ([ ...values.flat(), ...ratings ].some((value) => !Number.isFinite(value)))
78
+ return undefined
79
+ const best = Math.max(...ratings)
80
+ return {
81
+ before: lines.slice(0, h).join("\n").trim(),
82
+ after: lines.slice(end).join("\n").replace(/^\n+/, "").trimEnd(),
83
+ header,
84
+ rows: rows.map((row, k) => ({ cells: row, values: values[k]! })),
85
+ rating,
86
+ winners: new Set(ratings.flatMap((value, j) =>
87
+ value === best || best - value < TIE * Math.abs(best) ? [ j + 2 ] : []))
88
+ }
89
+ }
90
+
91
+ /* advance the sort by a clicked column: its primary order (ascending for
92
+ the criteria names, descending for the values), then its reverse order,
93
+ and then the original order again */
94
+ const advance = (sort: Sort | undefined, column: number): Sort | undefined => {
95
+ const primary: Order = column === 0 ? "asc" : "desc"
96
+ const reverse: Order = column === 0 ? "desc" : "asc"
97
+ if (sort?.column !== column)
98
+ return { column, order: primary }
99
+ return sort.order === primary ? { column, order: reverse } : undefined
100
+ }
101
+
102
+ /* order the criteria rows by the sort (stable, so equal rows keep their original order) */
103
+ const order = (rows: Matrix["rows"], sort: Sort | undefined): Matrix["rows"] => {
104
+ if (sort === undefined)
105
+ return rows
106
+ const sign = sort.order === "asc" ? 1 : -1
107
+ return [ ...rows ].sort((a, b) => sign * (sort.column === 0 ?
108
+ strip(a.cells[0]!).localeCompare(strip(b.cells[0]!)) :
109
+ a.values[sort.column - 1]! - b.values[sort.column - 1]!))
110
+ }
111
+
112
+ /* the decision matrix: its state and its operations */
113
+ export const modEvaluate = () => {
114
+ /* the last known width of the terminal and the sorts of the matrices */
115
+ let columns = 80
116
+ const sorts = new Map<string, Sort>()
117
+
118
+ /* sort the criteria rows of a matrix by a clicked header cell and redraw it */
119
+ const press = (engine: Pick<Engine, "invalidate">, request: string, column: number) => () => {
120
+ const sort = advance(sorts.get(request), column)
121
+ if (sort !== undefined)
122
+ sorts.set(request, sort)
123
+ else
124
+ sorts.delete(request)
125
+ engine.invalidate()
126
+ }
127
+
128
+ /* redraw a reply which contains a decision matrix (terminal only),
129
+ resp. draw nothing for any other reply */
130
+ const render = (e: Frozen<MatchedEvent<"ui.render", { component: "AssistantMessage", surface: "terminal" }>>,
131
+ ui: Elements["terminal"], engine: Pick<Engine, "invalidate">): RenderElement | undefined => {
132
+ /* act on decision matrices only */
133
+ columns = e.viewport?.columns ?? columns
134
+ const matrix = parse(e.props.text)
135
+ if (matrix === undefined || matrix.before.length > LIMIT || matrix.after.length > LIMIT)
136
+ return undefined
137
+
138
+ /* style the cells: the header ones in bold, the criteria rows in their sort
139
+ order, and the columns of the best alternatives in blue and bold */
140
+ const sort = sorts.get(e.requestId)
141
+ const tint = (chars: Char[], column: number): Char[] =>
142
+ matrix.winners.has(column) ? chars.map((char) => ({ ...char, bold: true, color: WINNER })) : chars
143
+ const head = matrix.header.map((cell, c) => tint(style(`**${cell}**`, undefined), c))
144
+ const body = [ ...order(matrix.rows, sort).map((row) => row.cells), matrix.rating ]
145
+ .map((row) => row.map((cell, c) => tint(style(c === 0 ? strip(cell) : cell, undefined), c)))
146
+
147
+ /* determine the column widths: the value columns at their natural width
148
+ (the header cells with room for the sort mark), the criteria column
149
+ within the remaining columns (gutter, grid lines, cell paddings, and a
150
+ spare column subtracted), else leave the matrix to the engine */
151
+ const widths = head.map((cell, c) =>
152
+ Math.max(width(cell) + MARK.none.length, ...body.map((row) => width(row[c]!))))
153
+ const avail = columns - 2 - (3 * head.length + 1) - 1 - widths.slice(1).reduce((sum, w) => sum + w, 0)
154
+ if (avail < Math.max(NARROW, width(head[0]!) + MARK.none.length))
155
+ return undefined
156
+ widths[0] = Math.min(widths[0]!, avail)
157
+
158
+ /* resolve the UI elements */
159
+ const { Box, Text, Button, Markdown } = ui
160
+
161
+ /* pad the parts of a cell line of the given width to its column width:
162
+ left-aligned for the criteria, right-aligned for the values */
163
+ const align = <T>(c: number, used: number, parts: T[]): (T | string)[] => {
164
+ const pad = " ".repeat(Math.max(0, widths[c]! - used))
165
+ return c === 0 ? [ ...parts, pad ] : [ pad, ...parts ]
166
+ }
167
+
168
+ /* draw a header cell: its styled text inside a hover-scoping box, which while
169
+ hovered reveals a row of clickable plain buttons (one per styled run, as in
170
+ the grill table) on top of it, followed by its sort mark */
171
+ const heading = (c: number): RenderElement[] => {
172
+ const group = `h${c}`
173
+ const onPress = press(engine, e.requestId, c)
174
+ const buttons = split(head[c]!).map(({ text, bold }, r) =>
175
+ Button({ key: `${group}/${r}`, label: text, plain: true, onPress,
176
+ hover: { scope: group, inverse: true, bold } }))
177
+ return align(c, width(head[c]!) + MARK.none.length, [
178
+ Box({ key: group, hover: { scope: group }, children: [
179
+ Text({ children: paint(Text, head[c]!) }),
180
+ Box({ position: "absolute", top: 0, left: 0, display: "none",
181
+ hover: { scope: group, display: "flex" }, flexDirection: "row", children: buttons })
182
+ ] }),
183
+ Text({ children: sort?.column === c ? MARK[sort.order] : MARK.none })
184
+ ]).map((part) => typeof part === "string" ? Text({ children: part }) : part)
185
+ }
186
+
187
+ /* draw a line of the grid out of the parts of its cells, resp. a rule */
188
+ const draw = (parts: RenderElement[][]) => Box({ flexDirection: "row", children: [
189
+ Text({ children: "│ " }),
190
+ ...parts.flatMap((part, c) => c > 0 ? [ Text({ children: " │ " }), ...part ] : part),
191
+ Text({ children: " │" })
192
+ ] })
193
+ const rule = (l: string, m: string, r: string) =>
194
+ Text({ wrap: "truncate-end", children: l + widths.map((w) => "─".repeat(w + 2)).join(m) + r })
195
+
196
+ /* draw the matrix as a grid: a rule below the header and above the rating
197
+ row, and the criteria wrapped within their column */
198
+ const grid: RenderElement[] = [
199
+ rule("┌", "┬", "┐"),
200
+ draw(head.map((_, c) => heading(c))),
201
+ rule("├", "┼", "┤")
202
+ ]
203
+ body.forEach((row, k) => {
204
+ if (k === body.length - 1)
205
+ grid.push(rule("├", "┼", "┤"))
206
+ wrap(row[0]!, widths[0]!, INDENT).forEach((line, i) =>
207
+ grid.push(draw(row.map((cell, c) => {
208
+ const chars = c === 0 ? line : i === 0 ? cell : []
209
+ return [ Text({ children: align(c, width(chars), paint(Text, chars)) }) ]
210
+ }))))
211
+ })
212
+ grid.push(rule("└", "┴", "┘"))
213
+
214
+ /* mimic the reply row of the engine: blank row above and bullet gutter */
215
+ return Box({ flexDirection: "row", marginTop: 1, width: "100%", children: [
216
+ Box({ flexShrink: 0, minWidth: 2, children: Text({ children: e.props.isFirstOfReply ? "⏺" : " " }) }),
217
+ Box({ flexDirection: "column", flexGrow: 1, flexShrink: 1, children: [
218
+ ...(matrix.before !== "" ? [ Markdown({ text: matrix.before }) ] : []),
219
+ Box({ flexDirection: "column", marginTop: matrix.before !== "" ? 1 : 0, children: grid }),
220
+ ...(matrix.after !== "" ? [ Box({ marginTop: 1, children: Markdown({ text: matrix.after }) }) ] : [])
221
+ ] })
222
+ ] })
223
+ }
224
+
225
+ return { render }
226
+ }
227
+