@windyroad/architect 0.9.0-preview.416 → 0.9.1-preview.423
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/agents/agent.md +3 -1
- package/agents/test/architect-needs-direction-verdict.bats +9 -0
- package/bin/wr-architect-is-decision-unconfirmed +3 -0
- package/package.json +1 -1
- package/scripts/is-decision-unconfirmed.sh +81 -0
- package/scripts/test/is-decision-unconfirmed.bats +108 -0
package/agents/agent.md
CHANGED
|
@@ -136,7 +136,7 @@ If a new decision must be recorded but it has 2+ viable options and no pinned di
|
|
|
136
136
|
>
|
|
137
137
|
> A decision must be recorded but the option is not pinned — the user, not the agent, owns this choice.
|
|
138
138
|
>
|
|
139
|
-
> - **Decision question**: <the question to settle, in one line>
|
|
139
|
+
> - **Decision question**: <the substantive question to settle, in one line>
|
|
140
140
|
> - **Option A** — <name + one-line, grounded in what you read>
|
|
141
141
|
> - **Option B** — <name + one-line, grounded in what you read>
|
|
142
142
|
> - (further options as applicable; include "do nothing / status quo" where relevant)
|
|
@@ -144,6 +144,8 @@ If a new decision must be recorded but it has 2+ viable options and no pinned di
|
|
|
144
144
|
>
|
|
145
145
|
> The main agent (or calling skill) translates this into an `AskUserQuestion` before the decision is recorded — never a prose ask. Under an AFK orchestrator that cannot ask mid-loop, the verdict queues to the iteration's `outstanding_questions` for batched return-presentation (ADR-044 (Decision-delegation contract)), never blocking or guessing.
|
|
146
146
|
|
|
147
|
+
**Name the SUBSTANCE, not the grain (ADR-074 (Confirm a decision's substance before building dependent work)).** The decision question + options MUST be the **substantive choice** — the load-bearing options the decision actually records. Do NOT substitute a meta/grain framing question (e.g. "one ADR or two?", file split, naming, ID series) for the substance. A grain question is not a decision the user needs to own; the substantive option is. If both a grain question AND a substantive choice exist, surface the substance — the grain is mechanical (decide it yourself per P132). Confirming only the grain leaves the load-bearing content unconfirmed, which is exactly the P315 failure ADR-074 closes: dependent work then gets built on an unconfirmed substantive choice and a later rejection forces rework.
|
|
148
|
+
|
|
147
149
|
### When to emit Needs Direction (per ADR-064)
|
|
148
150
|
|
|
149
151
|
Emit **NEEDS DIRECTION** only when ALL of the following hold:
|
|
@@ -49,6 +49,15 @@ setup() {
|
|
|
49
49
|
[ "$status" -eq 0 ]
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
@test "agent.md requires Needs-Direction to name the SUBSTANCE not the grain (ADR-074)" {
|
|
53
|
+
# ADR-074 surface 1: the verdict must name the substantive choice, not a
|
|
54
|
+
# meta/grain framing question (e.g. "one ADR or two?"). Closes the P315 vector.
|
|
55
|
+
run grep -niE "Name the SUBSTANCE, not the grain" "$AGENT_FILE"
|
|
56
|
+
[ "$status" -eq 0 ]
|
|
57
|
+
run grep -n "ADR-074" "$AGENT_FILE"
|
|
58
|
+
[ "$status" -eq 0 ]
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
@test "agent.md performance-review section cites ADR-026 as parent (ADR-026 Confirmation item 1)" {
|
|
53
62
|
run grep -nE "Runtime-Path Performance Review \(per ADR-026" "$AGENT_FILE"
|
|
54
63
|
[ "$status" -eq 0 ]
|
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# wr-architect — predicate: is a referenced decision unconfirmed? (ADR-074)
|
|
3
|
+
#
|
|
4
|
+
# Single-ADR sibling of detect-unoversighted.sh (ADR-066). Where the detector
|
|
5
|
+
# LISTS the whole unoversighted set and always exits 0, this answers ONE
|
|
6
|
+
# question for ONE ADR via its EXIT CODE — for the ADR-074 build-upon guard at
|
|
7
|
+
# the propose-fix surface (/wr-itil:manage-problem + /wr-itil:work-problems).
|
|
8
|
+
# A separate script (not a mode flag on the detector) keeps the detector's
|
|
9
|
+
# "always exit 0, path-list on stdout" contract intact (architect verdict
|
|
10
|
+
# 2026-05-27).
|
|
11
|
+
#
|
|
12
|
+
# "Unconfirmed" mirrors detect-unoversighted.sh EXACTLY:
|
|
13
|
+
# - the ADR's frontmatter lacks `human-oversight: confirmed`, AND
|
|
14
|
+
# - the ADR is not superseded.
|
|
15
|
+
# CANONICAL SHAPE: detect-unoversighted.sh. Keep the frontmatter-extraction
|
|
16
|
+
# awk block + the `human-oversight: confirmed` grep + the superseded skip in
|
|
17
|
+
# sync with that script. The `@test "agrees with detect-unoversighted ..."`
|
|
18
|
+
# case in test/is-decision-unconfirmed.bats fails if these two drift.
|
|
19
|
+
#
|
|
20
|
+
# Usage:
|
|
21
|
+
# is-decision-unconfirmed.sh <adr-ref> [DECISIONS_DIR]
|
|
22
|
+
# <adr-ref> = ADR-NNN | NNN | path/to/NNN-title.<state>.md
|
|
23
|
+
# DECISIONS_DIR defaults to docs/decisions
|
|
24
|
+
#
|
|
25
|
+
# Exit codes:
|
|
26
|
+
# 0 = unconfirmed — the build-upon guard SHOULD fire. Prints the resolved path.
|
|
27
|
+
# 1 = confirmed OR superseded — guard should NOT fire. No stdout.
|
|
28
|
+
# 2 = not found / unparseable ref. No stdout; reason on stderr.
|
|
29
|
+
|
|
30
|
+
set -uo pipefail
|
|
31
|
+
|
|
32
|
+
REF="${1:-}"
|
|
33
|
+
DECISIONS_DIR="${2:-docs/decisions}"
|
|
34
|
+
|
|
35
|
+
[ -n "$REF" ] || { echo "is-decision-unconfirmed: missing <adr-ref>" >&2; exit 2; }
|
|
36
|
+
|
|
37
|
+
# ── Resolve the ADR file ──────────────────────────────────────────────────
|
|
38
|
+
file=""
|
|
39
|
+
if [ -f "$REF" ]; then
|
|
40
|
+
file="$REF"
|
|
41
|
+
else
|
|
42
|
+
# Extract the leading numeric ID from ADR-NNN / NNN.
|
|
43
|
+
num="$(printf '%s' "$REF" | grep -oE '[0-9]+' | head -1)"
|
|
44
|
+
[ -n "$num" ] || { echo "is-decision-unconfirmed: cannot parse ADR id from '$REF'" >&2; exit 2; }
|
|
45
|
+
# Match flat (docs/decisions/NNN-*.md) + per-state subdir (docs/decisions/*/NNN-*.md)
|
|
46
|
+
# layouts; first match wins.
|
|
47
|
+
shopt -s nullglob
|
|
48
|
+
for cand in "$DECISIONS_DIR"/"$num"-*.md "$DECISIONS_DIR"/*/"$num"-*.md; do
|
|
49
|
+
file="$cand"; break
|
|
50
|
+
done
|
|
51
|
+
shopt -u nullglob
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
[ -n "$file" ] && [ -f "$file" ] || {
|
|
55
|
+
echo "is-decision-unconfirmed: no decision file for '$REF' under $DECISIONS_DIR" >&2
|
|
56
|
+
exit 2
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
base="$(basename "$file")"
|
|
60
|
+
|
|
61
|
+
# Superseded decisions are retired — a newer ADR replaced them. The build-upon
|
|
62
|
+
# guard does not fire (mirror of detect-unoversighted.sh's superseded skip).
|
|
63
|
+
case "$base" in *.superseded.md) exit 1 ;; esac
|
|
64
|
+
|
|
65
|
+
# Extract the frontmatter block (mirror of detect-unoversighted.sh): lines
|
|
66
|
+
# between the leading `---` and the next `---`. No leading `---` ⇒ no
|
|
67
|
+
# frontmatter ⇒ treated as unconfirmed.
|
|
68
|
+
fm="$(awk '
|
|
69
|
+
NR==1 && $0 != "---" { exit }
|
|
70
|
+
NR==1 { next }
|
|
71
|
+
/^---[[:space:]]*$/ { exit }
|
|
72
|
+
{ print }
|
|
73
|
+
' "$file")"
|
|
74
|
+
|
|
75
|
+
if printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$'; then
|
|
76
|
+
exit 1 # confirmed — OK to build on
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Unconfirmed — the build-upon guard SHOULD fire. Name the file for the guard.
|
|
80
|
+
echo "$file"
|
|
81
|
+
exit 0
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# ADR-074: is-decision-unconfirmed.sh is the single-ADR predicate for the
|
|
4
|
+
# build-upon guard (manage-problem / work-problems propose-fix surface). It
|
|
5
|
+
# answers "is this referenced decision unconfirmed?" via its EXIT CODE, where
|
|
6
|
+
# "unconfirmed" mirrors detect-unoversighted.sh EXACTLY (frontmatter lacks
|
|
7
|
+
# `human-oversight: confirmed`, and the ADR is not superseded).
|
|
8
|
+
#
|
|
9
|
+
# Behavioural — exercises the script against fixture trees and asserts on its
|
|
10
|
+
# exit code + stdout, not its source text (ADR-052).
|
|
11
|
+
|
|
12
|
+
setup() {
|
|
13
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
14
|
+
SCRIPT="$REPO_ROOT/packages/architect/scripts/is-decision-unconfirmed.sh"
|
|
15
|
+
DETECT="$REPO_ROOT/packages/architect/scripts/detect-unoversighted.sh"
|
|
16
|
+
DIR="$(mktemp -d)"
|
|
17
|
+
mkdir -p "$DIR/docs/decisions"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
teardown() {
|
|
21
|
+
rm -rf "$DIR"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# mk <filename> <status> [extra-frontmatter-lines...]
|
|
25
|
+
mk() {
|
|
26
|
+
local name="$1"; local status="$2"; shift 2
|
|
27
|
+
{
|
|
28
|
+
echo "---"
|
|
29
|
+
echo "status: \"$status\""
|
|
30
|
+
echo "date: 2026-05-27"
|
|
31
|
+
for line in "$@"; do echo "$line"; done
|
|
32
|
+
echo "---"
|
|
33
|
+
echo "# $name"
|
|
34
|
+
} > "$DIR/docs/decisions/$name"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@test "proposed ADR without the marker is unconfirmed (exit 0, prints path)" {
|
|
38
|
+
mk "100-no-marker.proposed.md" "proposed"
|
|
39
|
+
run bash "$SCRIPT" "ADR-100" "$DIR/docs/decisions"
|
|
40
|
+
[ "$status" -eq 0 ]
|
|
41
|
+
[[ "$output" == *"100-no-marker.proposed.md"* ]]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@test "proposed ADR carrying human-oversight: confirmed is confirmed (exit 1, no stdout)" {
|
|
45
|
+
mk "101-confirmed.proposed.md" "proposed" "human-oversight: confirmed" "oversight-date: 2026-05-27"
|
|
46
|
+
run bash "$SCRIPT" "ADR-101" "$DIR/docs/decisions"
|
|
47
|
+
[ "$status" -eq 1 ]
|
|
48
|
+
[ -z "$output" ]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@test "accepted ADR without the marker is still unconfirmed (exit 0)" {
|
|
52
|
+
mk "102-accepted-no-marker.accepted.md" "accepted"
|
|
53
|
+
run bash "$SCRIPT" "ADR-102" "$DIR/docs/decisions"
|
|
54
|
+
[ "$status" -eq 0 ]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "superseded ADR (even without marker) does NOT fire the guard (exit 1)" {
|
|
58
|
+
mk "103-retired.superseded.md" "superseded"
|
|
59
|
+
run bash "$SCRIPT" "ADR-103" "$DIR/docs/decisions"
|
|
60
|
+
[ "$status" -eq 1 ]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@test "a bare numeric ref resolves" {
|
|
64
|
+
mk "104-bare-num.proposed.md" "proposed"
|
|
65
|
+
run bash "$SCRIPT" "104" "$DIR/docs/decisions"
|
|
66
|
+
[ "$status" -eq 0 ]
|
|
67
|
+
[[ "$output" == *"104-bare-num.proposed.md"* ]]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@test "a direct path ref resolves" {
|
|
71
|
+
mk "105-by-path.proposed.md" "proposed"
|
|
72
|
+
run bash "$SCRIPT" "$DIR/docs/decisions/105-by-path.proposed.md"
|
|
73
|
+
[ "$status" -eq 0 ]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@test "a per-state subdir layout resolves" {
|
|
77
|
+
mkdir -p "$DIR/docs/decisions/proposed"
|
|
78
|
+
{
|
|
79
|
+
echo "---"; echo "status: \"proposed\""; echo "date: 2026-05-27"; echo "---"; echo "# subdir"
|
|
80
|
+
} > "$DIR/docs/decisions/proposed/106-subdir.md"
|
|
81
|
+
run bash "$SCRIPT" "ADR-106" "$DIR/docs/decisions"
|
|
82
|
+
[ "$status" -eq 0 ]
|
|
83
|
+
[[ "$output" == *"106-subdir.md"* ]]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@test "an unknown ADR ref exits 2 (not found)" {
|
|
87
|
+
run bash "$SCRIPT" "ADR-999" "$DIR/docs/decisions"
|
|
88
|
+
[ "$status" -eq 2 ]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@test "an unparseable ref exits 2" {
|
|
92
|
+
run bash "$SCRIPT" "not-an-adr" "$DIR/docs/decisions"
|
|
93
|
+
[ "$status" -eq 2 ]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@test "agrees with detect-unoversighted on the same fixture (sync guard)" {
|
|
97
|
+
# The two scripts share the frontmatter/marker/superseded shape. This guard
|
|
98
|
+
# fails if a future edit drifts one from the other.
|
|
99
|
+
mk "110-unconfirmed.proposed.md" "proposed"
|
|
100
|
+
mk "111-confirmed.proposed.md" "proposed" "human-oversight: confirmed" "oversight-date: 2026-05-27"
|
|
101
|
+
detect_out="$(bash "$DETECT" "$DIR/docs/decisions")"
|
|
102
|
+
# 110 is in the detector's unoversighted list AND the predicate exits 0.
|
|
103
|
+
[[ "$detect_out" == *"110-unconfirmed.proposed.md"* ]]
|
|
104
|
+
run bash "$SCRIPT" "ADR-110" "$DIR/docs/decisions"; [ "$status" -eq 0 ]
|
|
105
|
+
# 111 is NOT in the detector's list AND the predicate exits 1.
|
|
106
|
+
[[ "$detect_out" != *"111-confirmed.proposed.md"* ]]
|
|
107
|
+
run bash "$SCRIPT" "ADR-111" "$DIR/docs/decisions"; [ "$status" -eq 1 ]
|
|
108
|
+
}
|