@windyroad/architect 0.15.3 → 0.15.4

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.15.3"
126
+ "version": "0.15.4"
127
127
  }
@@ -124,7 +124,7 @@ cat <<EOF
124
124
  "hookSpecificOutput": {
125
125
  "hookEventName": "PreToolUse",
126
126
  "permissionDecision": "deny",
127
- "permissionDecisionReason": "BLOCKED: Cannot edit '${BASENAME}' without architecture review. You MUST first delegate to wr-architect:agent using the Agent tool (subagent_type: 'wr-architect:agent'). The architect will review against existing decisions in docs/decisions/ and flag if a new decision should be documented. After the review completes, this file will be unblocked automatically."
127
+ "permissionDecisionReason": "BLOCKED: Cannot edit '${BASENAME}' without architecture review. You MUST first delegate to wr-architect:agent using the Agent tool (subagent_type: 'wr-architect:agent'). The architect will review against existing decisions in docs/decisions/ and flag if a new decision should be documented. After the review completes, this file will be unblocked automatically. ${ARCHITECT_GATE_REASON}"
128
128
  }
129
129
  }
130
130
  EOF
@@ -35,7 +35,7 @@ cat <<EOF
35
35
  "hookSpecificOutput": {
36
36
  "hookEventName": "PreToolUse",
37
37
  "permissionDecision": "deny",
38
- "permissionDecisionReason": "BLOCKED: Architect must review the plan file before exiting plan mode. You MUST first delegate to wr-architect:agent using the Agent tool (subagent_type: 'wr-architect:agent') to review the plan against existing decisions in docs/decisions/. After the review completes, this will be unblocked automatically."
38
+ "permissionDecisionReason": "BLOCKED: Architect must review the plan file before exiting plan mode. You MUST first delegate to wr-architect:agent using the Agent tool (subagent_type: 'wr-architect:agent') to review the plan against existing decisions in docs/decisions/. After the review completes, this will be unblocked automatically. ${ARCHITECT_GATE_REASON}"
39
39
  }
40
40
  }
41
41
  EOF
@@ -8,6 +8,12 @@ _ARCHITECT_GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
8
  source "$_ARCHITECT_GATE_DIR/gate-helpers.sh"
9
9
 
10
10
  # Check architect gate marker. Returns 0 if marker is valid (allow), 1 if invalid (deny).
11
+ # Sets ARCHITECT_GATE_REASON on failure with an explicit recovery directive
12
+ # naming the wr-architect:agent subagent_type (P215 / RFC-021 — mirrors the
13
+ # sibling REVIEW_GATE_REASON pattern in review-gate.sh). Downstream
14
+ # enforcement hooks (architect-enforce-edit.sh, architect-plan-enforce.sh)
15
+ # append this reason to their BLOCKED deny message so the agent sees a clear
16
+ # recovery affordance without having to read source.
11
17
  # Usage: check_architect_gate "$SESSION_ID"
12
18
  check_architect_gate() {
13
19
  local SESSION_ID="$1"
@@ -38,6 +44,7 @@ check_architect_gate() {
38
44
  fi
39
45
  if [ "$STORED" != "$CURRENT" ]; then
40
46
  rm -f "$MARKER" "$HASH_FILE"
47
+ ARCHITECT_GATE_REASON="Decision drift detected — docs/decisions/ changed substantively since the last architect review. Re-delegate to wr-architect:agent via the Agent tool (subagent_type: 'wr-architect:agent') to refresh the marker."
41
48
  return 1 # Drift detected, deny
42
49
  else
43
50
  touch "$MARKER" # Slide TTL window forward
@@ -49,10 +56,12 @@ check_architect_gate() {
49
56
  fi
50
57
  else
51
58
  rm -f "$MARKER"
59
+ ARCHITECT_GATE_REASON="Architect review expired (${AGE}s old, TTL ${TTL_SECONDS}s). Re-delegate to wr-architect:agent via the Agent tool (subagent_type: 'wr-architect:agent') to refresh the marker."
52
60
  return 1 # TTL expired, deny
53
61
  fi
54
62
  fi
55
63
 
64
+ ARCHITECT_GATE_REASON="No architect review marker found for this session. Delegate to wr-architect:agent via the Agent tool (subagent_type: 'wr-architect:agent') so the architect can review and create the marker."
56
65
  return 1 # No marker, deny
57
66
  }
58
67
 
@@ -30,3 +30,59 @@ teardown() {
30
30
  ARCHITECT_TTL=0 run check_architect_gate "$TEST_SESSION"
31
31
  [ "$status" -ne 0 ]
32
32
  }
33
+
34
+ # P215 / RFC-021 — ARCHITECT_GATE_REASON behaviour. The gate must expose a
35
+ # differentiated reason per failure mode (no marker / TTL expired / drift
36
+ # detected) so the downstream deny message carries a clear recovery directive.
37
+ # Mirrors sibling REVIEW_GATE_REASON pattern in jtbd/voice-tone/style-guide
38
+ # review-gate.sh.
39
+
40
+ @test "ARCHITECT_GATE_REASON names re-delegate directive when no marker" {
41
+ ARCHITECT_GATE_REASON=""
42
+ check_architect_gate "$TEST_SESSION" || true
43
+ [[ "$ARCHITECT_GATE_REASON" == *"wr-architect:agent"* ]]
44
+ [[ "$ARCHITECT_GATE_REASON" == *"Agent tool"* ]]
45
+ }
46
+
47
+ @test "ARCHITECT_GATE_REASON names re-delegate directive when TTL expired" {
48
+ touch "/tmp/architect-reviewed-${TEST_SESSION}"
49
+ ARCHITECT_GATE_REASON=""
50
+ ARCHITECT_TTL=0 check_architect_gate "$TEST_SESSION" || true
51
+ [[ "$ARCHITECT_GATE_REASON" == *"expired"* ]]
52
+ [[ "$ARCHITECT_GATE_REASON" == *"wr-architect:agent"* ]]
53
+ [[ "$ARCHITECT_GATE_REASON" == *"refresh the marker"* ]]
54
+ }
55
+
56
+ @test "ARCHITECT_GATE_REASON names drift directive when stored hash differs" {
57
+ # Set up project root with a docs/decisions directory and a stored hash that
58
+ # differs from the current substance hash to force the drift branch.
59
+ TEST_PROJECT_DIR=$(mktemp -d)
60
+ mkdir -p "$TEST_PROJECT_DIR/docs/decisions"
61
+ echo "# ADR-001 current content" > "$TEST_PROJECT_DIR/docs/decisions/001-x.md"
62
+ touch "/tmp/architect-reviewed-${TEST_SESSION}"
63
+ echo "stale-hash-that-will-not-match" > "/tmp/architect-reviewed-${TEST_SESSION}.hash"
64
+ ARCHITECT_GATE_REASON=""
65
+ CLAUDE_PROJECT_DIR="$TEST_PROJECT_DIR" check_architect_gate "$TEST_SESSION" || true
66
+ rm -rf "$TEST_PROJECT_DIR"
67
+ [[ "$ARCHITECT_GATE_REASON" == *"drift"* ]] || [[ "$ARCHITECT_GATE_REASON" == *"changed"* ]]
68
+ [[ "$ARCHITECT_GATE_REASON" == *"wr-architect:agent"* ]]
69
+ [[ "$ARCHITECT_GATE_REASON" == *"refresh the marker"* ]]
70
+ }
71
+
72
+ @test "architect-enforce-edit deny output includes ARCHITECT_GATE_REASON directive" {
73
+ HOOK="$SCRIPT_DIR/architect-enforce-edit.sh"
74
+ ORIG_DIR="$PWD"
75
+ TEST_DIR=$(mktemp -d)
76
+ cd "$TEST_DIR"
77
+ mkdir -p docs/decisions
78
+ echo "# adr stub" > docs/decisions/001-x.md
79
+ json="{\"tool_input\":{\"file_path\":\"$PWD/src/x.ts\"},\"session_id\":\"deny-test-$$\"}"
80
+ run bash -c "echo '$json' | bash '$HOOK'"
81
+ cd "$ORIG_DIR"
82
+ rm -rf "$TEST_DIR"
83
+ [[ "$output" == *"BLOCKED"* ]]
84
+ [[ "$output" == *"wr-architect:agent"* ]]
85
+ # No marker exists for this fresh session — deny reason must explicitly
86
+ # name the re-delegate directive (not vague "review required").
87
+ [[ "$output" == *"No architect review marker"* ]] || [[ "$output" == *"marker"* ]]
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/architect",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "bin": {
6
6
  "windyroad-architect": "./bin/install.mjs"