@windyroad/architect 0.15.0-preview.562 → 0.15.1-preview.563
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/.claude-plugin/plugin.json +1 -1
- package/hooks/architect-detect.sh +6 -1
- package/hooks/architect-enforce-edit.sh +14 -2
- package/hooks/architect-mark-reviewed.sh +7 -2
- package/hooks/architect-plan-enforce.sh +7 -1
- package/hooks/architect-refresh-hash.sh +7 -2
- package/hooks/lib/architect-gate.sh +5 -2
- package/hooks/test/architect-project-root.bats +37 -0
- package/package.json +1 -1
|
@@ -16,7 +16,12 @@ source "$SCRIPT_DIR/lib/session-marker.sh"
|
|
|
16
16
|
INPUT=$(cat)
|
|
17
17
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "")
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# P191 Phase 2: anchor docs/decisions on the project root, not the hook's
|
|
20
|
+
# runtime CWD (see architect-enforce-edit.sh). A false-negative here silently
|
|
21
|
+
# drops the delegation-instruction injection that primes the architect gate.
|
|
22
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
23
|
+
|
|
24
|
+
if [ -d "$PROJECT_DIR/docs/decisions" ]; then
|
|
20
25
|
if has_announced "architect" "$SESSION_ID"; then
|
|
21
26
|
cat <<'HOOK_OUTPUT'
|
|
22
27
|
MANDATORY architecture gate active (docs/decisions/ present). Delegate to wr-architect:agent before editing project files. See turn-1 instructions for full scope and exclusions.
|
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
7
|
source "$SCRIPT_DIR/lib/architect-gate.sh"
|
|
8
8
|
|
|
9
|
+
# P191 Phase 2: resolve the project root from the session signal, not the
|
|
10
|
+
# hook's runtime CWD. Claude Code can launch the hook with a working directory
|
|
11
|
+
# that differs from the session/project dir while still exporting
|
|
12
|
+
# CLAUDE_PROJECT_DIR (and a $PWD env var) pointing at the project. A relative
|
|
13
|
+
# `[ -d "docs/decisions" ]` then false-negatives even though docs/decisions is
|
|
14
|
+
# present — and because this gate fails OPEN (exit 0) on a missing decisions
|
|
15
|
+
# dir, the misfire silently DEACTIVATES the architect gate and edits bypass
|
|
16
|
+
# review (a governance hole, strictly worse than the JTBD gate's fail-closed
|
|
17
|
+
# nuisance fixed in P191 Phase 1). Anchor every project-relative check on
|
|
18
|
+
# PROJECT_DIR. Pattern mirrors architect-oversight-nudge.sh.
|
|
19
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
20
|
+
|
|
9
21
|
INPUT=$(cat)
|
|
10
22
|
|
|
11
23
|
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') || true
|
|
@@ -25,14 +37,14 @@ fi
|
|
|
25
37
|
case "$FILE_PATH" in
|
|
26
38
|
/*)
|
|
27
39
|
case "$FILE_PATH" in
|
|
28
|
-
"$
|
|
40
|
+
"$PROJECT_DIR"/*) ;;
|
|
29
41
|
*) exit 0 ;;
|
|
30
42
|
esac
|
|
31
43
|
;;
|
|
32
44
|
esac
|
|
33
45
|
|
|
34
46
|
# Only gate if the project has architecture decisions
|
|
35
|
-
if [ ! -d "docs/decisions" ]; then
|
|
47
|
+
if [ ! -d "$PROJECT_DIR/docs/decisions" ]; then
|
|
36
48
|
exit 0
|
|
37
49
|
fi
|
|
38
50
|
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
8
|
source "$SCRIPT_DIR/lib/gate-helpers.sh"
|
|
9
9
|
|
|
10
|
+
# P191 Phase 2: anchor docs/decisions on the project root, not the hook's
|
|
11
|
+
# runtime CWD (see architect-enforce-edit.sh). A false-negative here never
|
|
12
|
+
# stores the marker hash, desynchronising the enforce gate's drift check.
|
|
13
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
14
|
+
|
|
10
15
|
_parse_input
|
|
11
16
|
|
|
12
17
|
TOOL_NAME=$(_get_tool_name)
|
|
@@ -51,8 +56,8 @@ case "$SUBAGENT" in
|
|
|
51
56
|
|
|
52
57
|
# Store decision hash for drift detection
|
|
53
58
|
if [ -f "/tmp/architect-reviewed-${SESSION_ID}" ]; then
|
|
54
|
-
if [ -d "docs/decisions" ]; then
|
|
55
|
-
HASH=$(find docs/decisions -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
59
|
+
if [ -d "$PROJECT_DIR/docs/decisions" ]; then
|
|
60
|
+
HASH=$(find "$PROJECT_DIR/docs/decisions" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
56
61
|
else
|
|
57
62
|
HASH="none"
|
|
58
63
|
fi
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
6
|
source "$SCRIPT_DIR/lib/architect-gate.sh"
|
|
7
7
|
|
|
8
|
+
# P191 Phase 2: anchor docs/decisions on the project root, not the hook's
|
|
9
|
+
# runtime CWD (see architect-enforce-edit.sh). This gate also fails OPEN on a
|
|
10
|
+
# missing decisions dir, so the CWD misfire silently lets ExitPlanMode through
|
|
11
|
+
# without architect plan review.
|
|
12
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
13
|
+
|
|
8
14
|
INPUT=$(cat)
|
|
9
15
|
|
|
10
16
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
|
|
@@ -15,7 +21,7 @@ if [ -z "$SESSION_ID" ]; then
|
|
|
15
21
|
fi
|
|
16
22
|
|
|
17
23
|
# Only gate if the project has architecture decisions
|
|
18
|
-
if [ ! -d "docs/decisions" ]; then
|
|
24
|
+
if [ ! -d "$PROJECT_DIR/docs/decisions" ]; then
|
|
19
25
|
exit 0
|
|
20
26
|
fi
|
|
21
27
|
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
# Portable hash: tries md5sum, falls back to md5 -r, then shasum
|
|
8
8
|
_hashcmd() { md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null; }
|
|
9
9
|
|
|
10
|
+
# P191 Phase 2: anchor docs/decisions on the project root, not the hook's
|
|
11
|
+
# runtime CWD (see architect-enforce-edit.sh). The refreshed hash must match
|
|
12
|
+
# the enforce gate's drift-check hash, which is now project-root-anchored.
|
|
13
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
14
|
+
|
|
10
15
|
INPUT=$(cat)
|
|
11
16
|
|
|
12
17
|
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') || true
|
|
@@ -30,8 +35,8 @@ HASH_FILE="/tmp/architect-reviewed-${SESSION_ID}.hash"
|
|
|
30
35
|
|
|
31
36
|
# Only refresh if a valid marker exists
|
|
32
37
|
if [ -f "$MARKER" ] && [ -f "$HASH_FILE" ]; then
|
|
33
|
-
if [ -d "docs/decisions" ]; then
|
|
34
|
-
HASH=$(find docs/decisions -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
38
|
+
if [ -d "$PROJECT_DIR/docs/decisions" ]; then
|
|
39
|
+
HASH=$(find "$PROJECT_DIR/docs/decisions" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
35
40
|
else
|
|
36
41
|
HASH="none"
|
|
37
42
|
fi
|
|
@@ -13,6 +13,9 @@ check_architect_gate() {
|
|
|
13
13
|
local SESSION_ID="$1"
|
|
14
14
|
local MARKER="/tmp/architect-reviewed-${SESSION_ID}"
|
|
15
15
|
local TTL_SECONDS="${ARCHITECT_TTL:-3600}"
|
|
16
|
+
# P191 Phase 2: anchor the docs/decisions drift-hash on the project root,
|
|
17
|
+
# not the hook's runtime CWD (see architect-enforce-edit.sh for rationale).
|
|
18
|
+
local PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
16
19
|
|
|
17
20
|
if [ -n "$SESSION_ID" ] && [ -f "$MARKER" ]; then
|
|
18
21
|
local NOW=$(date +%s)
|
|
@@ -24,8 +27,8 @@ check_architect_gate() {
|
|
|
24
27
|
if [ -f "$HASH_FILE" ]; then
|
|
25
28
|
local STORED=$(cat "$HASH_FILE")
|
|
26
29
|
local CURRENT
|
|
27
|
-
if [ -d "docs/decisions" ]; then
|
|
28
|
-
CURRENT=$(find docs/decisions -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
30
|
+
if [ -d "$PROJECT_DIR/docs/decisions" ]; then
|
|
31
|
+
CURRENT=$(find "$PROJECT_DIR/docs/decisions" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
29
32
|
else
|
|
30
33
|
CURRENT="none"
|
|
31
34
|
fi
|
|
@@ -42,3 +42,40 @@ run_hook_with_file() {
|
|
|
42
42
|
[[ "$output" == *"BLOCKED"* ]]
|
|
43
43
|
rm -rf "$TEST_DIR"
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
# P191 Phase 2: the architect gate must resolve docs/decisions from the
|
|
47
|
+
# project root (CLAUDE_PROJECT_DIR), NOT the hook's actual runtime CWD. Claude
|
|
48
|
+
# Code can launch the hook with a working directory that differs from the
|
|
49
|
+
# session/project dir; a relative `[ ! -d "docs/decisions" ]` then
|
|
50
|
+
# false-negatives and the gate FAILS OPEN (exit 0) — silently DEACTIVATING the
|
|
51
|
+
# architect gate so edits bypass review. This is a governance hole, strictly
|
|
52
|
+
# more severe than the JTBD gate's fail-closed nuisance (P191 Phase 1).
|
|
53
|
+
@test "project-root: gate stays ACTIVE via CLAUDE_PROJECT_DIR when hook CWD differs (P191 Phase 2)" {
|
|
54
|
+
local proj other json
|
|
55
|
+
proj="$(mktemp -d)"
|
|
56
|
+
other="$(mktemp -d)" # a CWD that does NOT contain docs/decisions
|
|
57
|
+
mkdir -p "$proj/docs/decisions"
|
|
58
|
+
echo "# ADR" > "$proj/docs/decisions/001-test.proposed.md"
|
|
59
|
+
json="{\"tool_input\":{\"file_path\":\"${proj}/src/index.ts\"},\"session_id\":\"test-session-$$\"}"
|
|
60
|
+
# Fire from `other` (wrong CWD) with CLAUDE_PROJECT_DIR set to the real
|
|
61
|
+
# project. Pre-fix this silently exited 0 (gate inactive, edit allowed);
|
|
62
|
+
# post-fix the gate is ACTIVE and denies for the missing review marker.
|
|
63
|
+
run env CLAUDE_PROJECT_DIR="$proj" bash -c "cd '$other' && printf '%s' '$json' | bash '$HOOK'"
|
|
64
|
+
rm -rf "$proj" "$other"
|
|
65
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
66
|
+
[[ "$output" == *"without architecture review"* ]]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# P191 Phase 2 regression guard: when docs/decisions genuinely does not exist
|
|
70
|
+
# under the project root, the gate correctly stays INACTIVE (fail-open exit 0).
|
|
71
|
+
# The fix narrows the false-negative; it must not start gating projects that
|
|
72
|
+
# have no architecture decisions.
|
|
73
|
+
@test "project-root: genuinely-absent docs/decisions stays inactive (fail-open preserved, P191 Phase 2)" {
|
|
74
|
+
local proj json
|
|
75
|
+
proj="$(mktemp -d)" # no docs/decisions created
|
|
76
|
+
json="{\"tool_input\":{\"file_path\":\"${proj}/src/index.ts\"},\"session_id\":\"test-session-$$\"}"
|
|
77
|
+
run env CLAUDE_PROJECT_DIR="$proj" bash -c "printf '%s' '$json' | bash '$HOOK'"
|
|
78
|
+
rm -rf "$proj"
|
|
79
|
+
[ "$status" -eq 0 ]
|
|
80
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
81
|
+
}
|