@windyroad/architect 0.20.2 → 0.20.3-preview.1031

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.
@@ -123,5 +123,5 @@
123
123
  }
124
124
  },
125
125
  "name": "wr-architect",
126
- "version": "0.20.2"
126
+ "version": "0.20.3"
127
127
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wr-architect",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "author": {
6
6
  "name": "Windy Road Technology",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/architect",
3
- "version": "0.20.2",
3
+ "version": "0.20.3-preview.1031",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "bin": {
6
6
  "windyroad-architect": "./bin/install.mjs"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # check-amends-backreference.sh — load-bearing detector for lockstep-amendment drift.
3
+ #
4
+ # An ADR that declares `amends: [ADR-NNN, ...]` is asserting that it changed those
5
+ # decisions' meaning. A reader who lands on the AMENDED ADR must be able to find
6
+ # that out from the amended ADR itself — otherwise the old text reads as current
7
+ # and the amendment is invisible from the only direction that matters.
8
+ #
9
+ # This checks the back-reference exists: for every `amends:` entry, the amended
10
+ # ADR's body must mention the amending ADR by ID. Cheap, mechanical, and it fires
11
+ # at the moment the drift is introduced rather than at a retro months later
12
+ # (ADR-051 load-bearing-from-the-start; the R002 sub-class with no detector).
13
+ #
14
+ # Usage: check-amends-backreference.sh [decisions-dir=docs/decisions]
15
+ # Exit: 0 = every declared amendment is back-referenced; 1 = >=1 missing
16
+ # (offenders on stderr); 2 = usage / dir error.
17
+ #
18
+ # @adr ADR-051 (load-bearing from the start) ADR-031 (per-ADR body authoritative)
19
+ # ADR-052 (behavioural bats) ADR-101 (the five-artefact lockstep that
20
+ # surfaced the gap)
21
+ # @problem P456
22
+ set -uo pipefail
23
+
24
+ dir="${1:-docs/decisions}"
25
+ [ -d "$dir" ] || { echo "check-amends-backreference: not a directory: $dir" >&2; exit 2; }
26
+
27
+ missing=""
28
+ shopt -s nullglob
29
+ for f in "$dir"/[0-9]*.md; do
30
+ self="ADR-$(basename "$f" | grep -oE '^[0-9]+')"
31
+ # `amends:` is frontmatter-only; stop at the closing delimiter so a body
32
+ # mention of the word cannot be mistaken for a declaration.
33
+ amends_line="$(awk 'NR==1&&$0=="---"{fm=1;next} fm&&/^---[[:space:]]*$/{exit} fm&&/^amends:/{print;exit}' "$f")"
34
+ [ -n "$amends_line" ] || continue
35
+ for target in $(printf '%s' "$amends_line" | grep -oE 'ADR-[0-9]+'); do
36
+ # nullglob array, not `ls` — an unmatched glob passes through to ls as a
37
+ # literal and its diagnostics can land in the capture.
38
+ shopt -s nullglob; hits=("$dir"/"${target#ADR-}"-*.md); shopt -u nullglob
39
+ tf="${hits[0]:-}"
40
+ if [ -z "$tf" ]; then
41
+ missing="$missing\n $self declares amends: $target, which does not resolve in $dir"
42
+ continue
43
+ fi
44
+ grep -qF "$self" "$tf" || \
45
+ missing="$missing\n $(basename "$tf") is amended by $self but never mentions it — a reader of the amended decision cannot discover the amendment"
46
+ done
47
+ done
48
+ shopt -u nullglob
49
+
50
+ if [ -n "$missing" ]; then
51
+ # shellcheck disable=SC2059
52
+ printf "check-amends-backreference: lockstep-amendment drift:$missing\n" >&2
53
+ exit 1
54
+ fi
55
+ exit 0
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bats
2
+ # Behavioural tests for check-amends-backreference.sh.
3
+ # Runs the real script against real fixture trees and asserts on exit status and
4
+ # stderr — never on the script's source text (ADR-052 / P081).
5
+ #
6
+ # @adr ADR-051 ADR-052 @problem P456
7
+
8
+ setup() {
9
+ CHECK="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/check-amends-backreference.sh"
10
+ TMP="$(mktemp -d)"; cd "$TMP"; mkdir -p d
11
+ }
12
+ teardown() { cd /; rm -rf "$TMP"; }
13
+
14
+ amender() { printf -- '---\nstatus: "proposed"\namends: [%s]\n---\n\n# ADR-%s\n' "$2" "$1" > "d/$1-amender.md"; }
15
+ amended() { printf -- '---\nstatus: "proposed"\n---\n\n# ADR-%s\n\n%s\n' "$1" "$2" > "d/$1-amended.md"; }
16
+
17
+ @test "passes when the amended ADR back-references the amending one" {
18
+ amender 101 "ADR-090"
19
+ amended 090 "Exercised 2026-07-26 by ADR-101, which coarsens this rule."
20
+ run bash "$CHECK" d
21
+ [ "$status" -eq 0 ]
22
+ }
23
+
24
+ @test "fails when the amended ADR never mentions the amending one" {
25
+ amender 101 "ADR-090"
26
+ amended 090 "This decision stands unqualified."
27
+ run bash "$CHECK" d
28
+ [ "$status" -eq 1 ]
29
+ echo "$output" | grep -q '090'
30
+ echo "$output" | grep -q 'ADR-101'
31
+ }
32
+
33
+ @test "checks EVERY entry in a multi-target amends list, not just the first" {
34
+ amender 101 "ADR-090, ADR-095, ADR-096"
35
+ amended 090 "Amended by ADR-101."
36
+ amended 095 "Amended by ADR-101."
37
+ amended 096 "This decision stands unqualified."
38
+ run bash "$CHECK" d
39
+ [ "$status" -eq 1 ]
40
+ echo "$output" | grep -q '096'
41
+ ! echo "$output" | grep -q '095'
42
+ }
43
+
44
+ @test "fails when an amends: target does not resolve at all" {
45
+ amender 101 "ADR-777"
46
+ run bash "$CHECK" d
47
+ [ "$status" -eq 1 ]
48
+ echo "$output" | grep -q 'ADR-777'
49
+ }
50
+
51
+ @test "an ADR with no amends: declaration is not checked" {
52
+ amended 090 "A decision that amends nothing."
53
+ run bash "$CHECK" d
54
+ [ "$status" -eq 0 ]
55
+ }
56
+
57
+ @test "a body mention of the word amends is not read as a declaration" {
58
+ printf -- '---\nstatus: "proposed"\n---\n\n# ADR-101\n\nThis amends: nothing in particular, per ADR-999.\n' > d/101-prose.md
59
+ run bash "$CHECK" d
60
+ [ "$status" -eq 0 ]
61
+ }
62
+
63
+ @test "the live corpus satisfies the invariant" {
64
+ repo="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
65
+ [ -d "$repo/docs/decisions" ] || skip "not running in the source repo"
66
+ run bash "$CHECK" "$repo/docs/decisions"
67
+ [ "$status" -eq 0 ]
68
+ }
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "WR Architect: Capture ADR"
3
+ short_description: "Capture an architecture decision from current context"
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "WR Architect: Create ADR"
3
+ short_description: "Create a MADR architecture decision record"
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "WR Architect: Review Decisions"
3
+ short_description: "Confirm or amend unreviewed architecture decisions"
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "WR Architect: Review Design"
3
+ short_description: "Review changes against architecture decisions"