@windyroad/architect 0.4.1-preview.161 → 0.5.0-preview.168
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.
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Architecture - UserPromptSubmit hook
|
|
2
|
+
# Architecture - UserPromptSubmit hook (P095 / ADR-038)
|
|
3
3
|
# Detects docs/decisions/ directory and injects delegation instruction.
|
|
4
4
|
# The architect agent reviews changes against architectural decision records.
|
|
5
|
+
#
|
|
6
|
+
# Progressive disclosure (ADR-038): emit the full MANDATORY block only
|
|
7
|
+
# on the first prompt of a session; subsequent prompts see a terse
|
|
8
|
+
# reminder that names the gate + the delegation affordance. The
|
|
9
|
+
# PreToolUse edit gate (architect-enforce-edit.sh) remains the
|
|
10
|
+
# enforcement surface — the prose is a reminder, not the enforcer.
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
# shellcheck source=lib/session-marker.sh
|
|
14
|
+
source "$SCRIPT_DIR/lib/session-marker.sh"
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "")
|
|
5
18
|
|
|
6
19
|
if [ -d "docs/decisions" ]; then
|
|
7
|
-
|
|
20
|
+
if has_announced "architect" "$SESSION_ID"; then
|
|
21
|
+
cat <<'HOOK_OUTPUT'
|
|
22
|
+
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.
|
|
23
|
+
HOOK_OUTPUT
|
|
24
|
+
else
|
|
25
|
+
cat <<'HOOK_OUTPUT'
|
|
8
26
|
INSTRUCTION: MANDATORY ARCHITECTURE CHECK. YOU MUST FOLLOW THIS.
|
|
9
27
|
DETECTED: docs/decisions/ exists in this project.
|
|
10
28
|
|
|
@@ -41,6 +59,8 @@ docs/problems/ (problem tickets), docs/BRIEFING.md, RISK-POLICY.md,
|
|
|
41
59
|
docs/PRODUCT_DISCOVERY.md, docs/VOICE-AND-TONE.md,
|
|
42
60
|
docs/STYLE-GUIDE.md.
|
|
43
61
|
HOOK_OUTPUT
|
|
62
|
+
mark_announced "architect" "$SESSION_ID"
|
|
63
|
+
fi
|
|
44
64
|
else
|
|
45
65
|
cat <<'HOOK_OUTPUT'
|
|
46
66
|
NOTE: This project has no docs/decisions/ directory.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Shared session-announcement marker helpers (P095 / ADR-038).
|
|
3
|
+
#
|
|
4
|
+
# Used by UserPromptSubmit hooks to gate verbose MANDATORY instruction
|
|
5
|
+
# prose behind a once-per-session check. First prompt of a session emits
|
|
6
|
+
# the full block AND calls mark_announced; subsequent prompts see the
|
|
7
|
+
# marker via has_announced and emit only a terse reminder.
|
|
8
|
+
#
|
|
9
|
+
# Why no TTL or drift check (unlike review-gate.sh): announcement is
|
|
10
|
+
# bookkeeping for prose verbosity, not enforcement. PreToolUse gates
|
|
11
|
+
# still block unauthorised edits regardless of announcement state; the
|
|
12
|
+
# delegated agent re-reads policy when it runs. Extending the marker's
|
|
13
|
+
# lifetime across policy changes mid-session is safe — the gate, not
|
|
14
|
+
# the announcement, is load-bearing.
|
|
15
|
+
#
|
|
16
|
+
# Marker path convention: /tmp/${SYSTEM}-announced-${SESSION_ID}
|
|
17
|
+
# (mirrors the /tmp/${SYSTEM}-reviewed-${SESSION_ID} convention from
|
|
18
|
+
# style-guide/voice-tone/risk-scorer review-gate.sh; the -announced-
|
|
19
|
+
# suffix distinguishes announcement markers from clearance markers).
|
|
20
|
+
#
|
|
21
|
+
# Empty SESSION_ID fallback: has_announced returns 1 (not announced,
|
|
22
|
+
# full block emits) and mark_announced is a no-op (no file written).
|
|
23
|
+
# This covers manual hook invocation, test harnesses, and any rare
|
|
24
|
+
# case where Claude Code does not pass a session_id on stdin.
|
|
25
|
+
|
|
26
|
+
# Returns 0 if the hook for SYSTEM has already announced in SESSION_ID,
|
|
27
|
+
# 1 otherwise. Empty SESSION_ID => returns 1 (never announced).
|
|
28
|
+
#
|
|
29
|
+
# Usage: has_announced "architect" "$SESSION_ID"
|
|
30
|
+
has_announced() {
|
|
31
|
+
local SYSTEM="$1"
|
|
32
|
+
local SESSION_ID="$2"
|
|
33
|
+
[ -n "$SESSION_ID" ] || return 1
|
|
34
|
+
[ -f "/tmp/${SYSTEM}-announced-${SESSION_ID}" ]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Writes the announcement marker for SYSTEM in SESSION_ID. Empty
|
|
38
|
+
# SESSION_ID => no-op. Safe to call more than once per session.
|
|
39
|
+
#
|
|
40
|
+
# Usage: mark_announced "architect" "$SESSION_ID"
|
|
41
|
+
mark_announced() {
|
|
42
|
+
local SYSTEM="$1"
|
|
43
|
+
local SESSION_ID="$2"
|
|
44
|
+
[ -n "$SESSION_ID" ] || return 0
|
|
45
|
+
: > "/tmp/${SYSTEM}-announced-${SESSION_ID}"
|
|
46
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P095 / ADR-038: architect-detect.sh UserPromptSubmit hook must emit
|
|
4
|
+
# the full MANDATORY instruction block only on the first prompt of a
|
|
5
|
+
# session (identified by session_id on stdin). Subsequent prompts in
|
|
6
|
+
# the same session emit only a terse reminder. New sessions or empty
|
|
7
|
+
# session_ids fall back to the full block.
|
|
8
|
+
|
|
9
|
+
setup() {
|
|
10
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
11
|
+
HOOK="$REPO_ROOT/packages/architect/hooks/architect-detect.sh"
|
|
12
|
+
|
|
13
|
+
# Stub project working dir so the hook's docs/decisions/ detection fires.
|
|
14
|
+
WORKDIR="$(mktemp -d)"
|
|
15
|
+
mkdir -p "$WORKDIR/docs/decisions"
|
|
16
|
+
: > "$WORKDIR/docs/decisions/.keep"
|
|
17
|
+
|
|
18
|
+
SID="arch-detect-test-$$-$RANDOM"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
teardown() {
|
|
22
|
+
rm -f "/tmp/architect-announced-${SID}"
|
|
23
|
+
rm -f "/tmp/architect-announced-${SID}-alt"
|
|
24
|
+
rm -rf "$WORKDIR"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Helper: invoke the hook with a given session_id from stdin, while cd'd
|
|
28
|
+
# into WORKDIR so the `docs/decisions` detection picks up the stub.
|
|
29
|
+
run_hook() {
|
|
30
|
+
local sid="$1"
|
|
31
|
+
(cd "$WORKDIR" && echo "{\"session_id\":\"$sid\"}" | bash "$HOOK")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@test "architect-detect: first invocation emits the full MANDATORY block" {
|
|
35
|
+
run run_hook "$SID"
|
|
36
|
+
[ "$status" -eq 0 ]
|
|
37
|
+
[ "${#output}" -gt 500 ]
|
|
38
|
+
[[ "$output" == *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
39
|
+
[[ "$output" == *"wr-architect:agent"* ]]
|
|
40
|
+
[[ "$output" == *"SCOPE:"* ]]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@test "architect-detect: first invocation writes the announcement marker" {
|
|
44
|
+
run run_hook "$SID"
|
|
45
|
+
[ "$status" -eq 0 ]
|
|
46
|
+
[ -f "/tmp/architect-announced-${SID}" ]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@test "architect-detect: second invocation emits only a terse reminder" {
|
|
50
|
+
run_hook "$SID" >/dev/null
|
|
51
|
+
run run_hook "$SID"
|
|
52
|
+
[ "$status" -eq 0 ]
|
|
53
|
+
# Terse reminder is < 250 bytes; full block is > 500 bytes.
|
|
54
|
+
[ "${#output}" -lt 250 ]
|
|
55
|
+
# Terse reminder names the gate AND the delegation affordance.
|
|
56
|
+
[[ "$output" == *"architecture"* ]] || [[ "$output" == *"ARCHITECTURE"* ]]
|
|
57
|
+
[[ "$output" == *"wr-architect:agent"* ]]
|
|
58
|
+
# Full SCOPE block is NOT re-emitted.
|
|
59
|
+
[[ "$output" != *"Does NOT apply to"* ]]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@test "architect-detect: terse reminder preserves the MANDATORY signal word" {
|
|
63
|
+
run_hook "$SID" >/dev/null
|
|
64
|
+
run run_hook "$SID"
|
|
65
|
+
[ "$status" -eq 0 ]
|
|
66
|
+
# Per JTBD-lead condition: terse reminder must keep an imperative
|
|
67
|
+
# marker (MANDATORY / REQUIRED / NON-OPTIONAL) so the enforcement
|
|
68
|
+
# signal does not soften after turn 1.
|
|
69
|
+
[[ "$output" == *"MANDATORY"* ]] || [[ "$output" == *"REQUIRED"* ]] || [[ "$output" == *"NON-OPTIONAL"* ]]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@test "architect-detect: terse reminder names the trigger artifact" {
|
|
73
|
+
run_hook "$SID" >/dev/null
|
|
74
|
+
run run_hook "$SID"
|
|
75
|
+
[ "$status" -eq 0 ]
|
|
76
|
+
# Per JTBD-lead condition: reason for the gate must stay visible
|
|
77
|
+
# on subsequent turns.
|
|
78
|
+
[[ "$output" == *"docs/decisions"* ]]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@test "architect-detect: different session_id re-emits the full block" {
|
|
82
|
+
run_hook "$SID" >/dev/null
|
|
83
|
+
local SID2="${SID}-alt"
|
|
84
|
+
run run_hook "$SID2"
|
|
85
|
+
[ "$status" -eq 0 ]
|
|
86
|
+
[ "${#output}" -gt 500 ]
|
|
87
|
+
[[ "$output" == *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
88
|
+
rm -f "/tmp/architect-announced-${SID2}"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@test "architect-detect: empty session_id emits the full block and writes no marker" {
|
|
92
|
+
run run_hook ""
|
|
93
|
+
[ "$status" -eq 0 ]
|
|
94
|
+
[ "${#output}" -gt 500 ]
|
|
95
|
+
[[ "$output" == *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
96
|
+
[ ! -f "/tmp/architect-announced-" ]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@test "architect-detect: absent docs/decisions/ emits the no-decisions NOTE (unchanged by this ADR)" {
|
|
100
|
+
local NO_DECISIONS="$(mktemp -d)"
|
|
101
|
+
run bash -c "cd '$NO_DECISIONS' && echo '{\"session_id\":\"$SID\"}' | bash '$HOOK'"
|
|
102
|
+
[ "$status" -eq 0 ]
|
|
103
|
+
[[ "$output" == *"no docs/decisions/ directory"* ]]
|
|
104
|
+
[[ "$output" != *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
105
|
+
rm -rf "$NO_DECISIONS"
|
|
106
|
+
}
|