@windyroad/architect 0.10.0 → 0.11.0-preview.440

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.10.0"
126
+ "version": "0.11.0"
127
127
  }
package/agents/agent.md CHANGED
@@ -107,6 +107,7 @@ When a change includes a new or modified decision file in `docs/decisions/`:
107
107
  - Does it list at least 2 considered options with pros/cons?
108
108
  - Does it include reassessment criteria?
109
109
  - If it supersedes another decision, is the old decision properly updated?
110
+ - **Is `docs/decisions/README.md` (the compendium) refreshed and staged?** Per ADR-077, the compendium is the architect agent's routine load surface and MUST be regenerated whenever an ADR body changes. Skills (`/wr-architect:create-adr`, `/wr-architect:capture-adr`, `/wr-architect:review-decisions`) regenerate it automatically; off-skill hand-edits and bulk renames must regenerate it explicitly. Flag any change that touches `docs/decisions/<NNN>-*.md` without also staging a fresh `docs/decisions/README.md`. Recovery is mechanical: `wr-architect-generate-decisions-compendium && git add docs/decisions/README.md`. The `architect-compendium-refresh-discipline.sh` commit-time hook is the safety-net backstop, not the primary mechanism.
110
111
 
111
112
  ## Output Formatting
112
113
 
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ # architect-compendium-refresh-discipline.sh — PreToolUse:Bash hook
3
+ # (safety net per ADR-077). Denies `git commit` invocations whose staged
4
+ # set includes a `docs/decisions/<NNN>-*.md` ADR change but does NOT also
5
+ # stage a refreshed `docs/decisions/README.md` compendium that matches
6
+ # the current ADR bodies.
7
+ #
8
+ # Per ADR-077: the architect agent and the architect skills
9
+ # (`/wr-architect:create-adr`, `/wr-architect:capture-adr`,
10
+ # `/wr-architect:review-decisions`) are the PRIMARY mechanism for keeping
11
+ # the compendium fresh — they invoke `wr-architect-generate-decisions-compendium`
12
+ # at the right point in their flows. This hook is the SAFETY NET: it
13
+ # catches edits that bypass those flows (hand-edits via Edit/Write tools,
14
+ # off-skill bulk renames, direct file modifications). Mirrors the
15
+ # P165 `itil-readme-refresh-discipline.sh` pattern at the decisions surface.
16
+ #
17
+ # Allow paths (exit 0 silently per ADR-045 Pattern 1):
18
+ # - tool_name != "Bash"
19
+ # - command's leading effective executable is not `git commit`
20
+ # - `RISK_BYPASS: architect-compendium-deferred` token present in command
21
+ # (intentional follow-up refresh; same allow-list shape as the P165 +
22
+ # ADR-014 commit-message bypass-token pattern)
23
+ # - staged set has no `docs/decisions/<NNN>-*.md` ADR change
24
+ #
25
+ # Deny paths (exit 2 with PreToolUse deny JSON on stderr):
26
+ # - ADR staged but compendium not staged
27
+ # - both staged but staged compendium does not match generator output
28
+ # against current working-tree ADR bodies
29
+ #
30
+ # Recovery is mechanical per ADR-013 Rule 1:
31
+ # wr-architect-generate-decisions-compendium && git add docs/decisions/README.md
32
+ #
33
+ # Override (legitimate intentional split):
34
+ # append "RISK_BYPASS: architect-compendium-deferred" to the commit message
35
+ #
36
+ # Cross-ref: ADR-077 Confirmation item (h). See also packages/itil/hooks/itil-readme-refresh-discipline.sh
37
+ # for the P165 sibling pattern.
38
+
39
+ set -uo pipefail
40
+
41
+ # PreToolUse input arrives on stdin as JSON.
42
+ input=$(cat)
43
+
44
+ # Tool gate: only Bash.
45
+ tool_name=$(printf '%s' "$input" | jq -r '.tool_name // ""' 2>/dev/null)
46
+ [ "$tool_name" = "Bash" ] || exit 0
47
+
48
+ # Extract the command.
49
+ command=$(printf '%s' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
50
+ [ -n "$command" ] || exit 0
51
+
52
+ # Only fire on `git commit` invocations. Leading-executable check (P268
53
+ # pattern): substring "git commit" anywhere can match unrelated commands
54
+ # (e.g. grep / sed / cat with that literal). We check the first effective
55
+ # token sequence: optional env-var assignments + optional `git`-aliasing
56
+ # wrappers (none in this codebase) + the literal `git commit`.
57
+ echo "$command" | awk '
58
+ {
59
+ # Strip leading whitespace and env assignments (FOO=bar).
60
+ sub(/^[[:space:]]+/, "")
61
+ while ($0 ~ /^[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+/) {
62
+ sub(/^[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+/, "")
63
+ }
64
+ # Match git followed by commit.
65
+ if ($0 ~ /^git[[:space:]]+commit\b/) exit 0
66
+ exit 1
67
+ }
68
+ ' || exit 0
69
+
70
+ # Allow-list bypass token. Same shape as P165 and ADR-014.
71
+ if echo "$command" | grep -qF 'RISK_BYPASS: architect-compendium-deferred'; then
72
+ exit 0
73
+ fi
74
+
75
+ # Env-var bypass for batch/migration cases (parity with BYPASS_README_REFRESH_GATE).
76
+ if [ "${BYPASS_COMPENDIUM_REFRESH_GATE:-0}" = "1" ]; then
77
+ exit 0
78
+ fi
79
+
80
+ # Resolve repo root so subsequent git plumbing is path-stable.
81
+ repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
82
+ cd "$repo_root" || exit 0
83
+
84
+ # Inspect the staged set.
85
+ staged_adrs=$(git diff --cached --name-only 2>/dev/null \
86
+ | awk '/^docs\/decisions\/[0-9]+-.*\.md$/ { print }' \
87
+ | head -20)
88
+ [ -n "$staged_adrs" ] || exit 0
89
+
90
+ staged_compendium=$(git diff --cached --name-only 2>/dev/null \
91
+ | awk '/^docs\/decisions\/README\.md$/ { print }')
92
+
93
+ if [ -z "$staged_compendium" ]; then
94
+ # ADR staged but compendium not staged. Deny.
95
+ first_adr=$(echo "$staged_adrs" | head -1)
96
+ cat >&2 <<EOF
97
+ {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "architect-compendium-refresh-discipline: '${first_adr}' is staged for commit but 'docs/decisions/README.md' is NOT. The compendium is the architect agent's routine load surface (ADR-077). Run: wr-architect-generate-decisions-compendium && git add docs/decisions/README.md. Intentional follow-up split: append 'RISK_BYPASS: architect-compendium-deferred' to the commit message. Batch/migration: set BYPASS_COMPENDIUM_REFRESH_GATE=1."}}
98
+ EOF
99
+ exit 2
100
+ fi
101
+
102
+ # Both staged. Verify the staged compendium matches generator output for the
103
+ # current ADR bodies (working tree). The --check mode generates to temp, no
104
+ # mutation. Exit 0 => match; exit 1 => stale.
105
+ if ! wr-architect-generate-decisions-compendium --check >/dev/null 2>&1; then
106
+ cat >&2 <<EOF
107
+ {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "architect-compendium-refresh-discipline: 'docs/decisions/README.md' is staged but does NOT match the current ADR bodies (stale compendium). Run: wr-architect-generate-decisions-compendium && git add docs/decisions/README.md to refresh, then re-commit. Intentional follow-up split: append 'RISK_BYPASS: architect-compendium-deferred' to the commit message."}}
108
+ EOF
109
+ exit 2
110
+ fi
111
+
112
+ # All clear.
113
+ exit 0
package/hooks/hooks.json CHANGED
@@ -8,7 +8,8 @@
8
8
  ],
9
9
  "PreToolUse": [
10
10
  { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-enforce-edit.sh" }] },
11
- { "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-plan-enforce.sh" }] }
11
+ { "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-plan-enforce.sh" }] },
12
+ { "matcher": "Bash", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-compendium-refresh-discipline.sh" }] }
12
13
  ],
13
14
  "PostToolUse": [
14
15
  { "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-mark-reviewed.sh" }] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/architect",
3
- "version": "0.10.0",
3
+ "version": "0.11.0-preview.440",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "bin": {
6
6
  "windyroad-architect": "./bin/install.mjs"
@@ -23,14 +23,51 @@
23
23
 
24
24
  set -uo pipefail
25
25
 
26
+ # --- Flag parsing ----------------------------------------------------------
27
+ # `--check` (no write): generate to a temp file and diff against the on-disk
28
+ # compendium. Exit 0 if byte-identical, 1 if drift, 2 if directory missing.
29
+ # Used by the architect-compendium-refresh-discipline.sh enforcement hook
30
+ # (Slice 2) to verify the staged compendium matches the working-tree ADRs.
31
+ CHECK_MODE=0
32
+ case "${1:-}" in
33
+ --check)
34
+ CHECK_MODE=1
35
+ shift
36
+ ;;
37
+ --help|-h)
38
+ cat <<'EOF'
39
+ Usage: generate-decisions-compendium.sh [--check] [decisions_dir]
40
+
41
+ Without --check: regenerates <decisions_dir>/README.md from the per-ADR
42
+ bodies. Idempotent — same in-force ADR set produces byte-identical output.
43
+
44
+ With --check: generates to a temp file and diffs against the existing
45
+ <decisions_dir>/README.md. Exits 0 if up-to-date, 1 if stale (with a
46
+ diff hint), 2 on directory error. Does NOT modify any file. Used by the
47
+ ADR-077 enforcement hook to verify the committed compendium matches the
48
+ current ADR bodies.
49
+ EOF
50
+ exit 0
51
+ ;;
52
+ esac
53
+
26
54
  DECISIONS_DIR="${1:-docs/decisions}"
27
- COMPENDIUM="$DECISIONS_DIR/README.md"
55
+ TARGET_COMPENDIUM="$DECISIONS_DIR/README.md"
28
56
 
29
57
  if [ ! -d "$DECISIONS_DIR" ]; then
30
58
  echo "generate-decisions-compendium: decisions directory not found: $DECISIONS_DIR" >&2
31
59
  exit 2
32
60
  fi
33
61
 
62
+ # In check mode, redirect generation to a temp file so the on-disk
63
+ # compendium is never mutated.
64
+ if [ "$CHECK_MODE" = "1" ]; then
65
+ COMPENDIUM=$(mktemp -t architect-compendium-check.XXXXXX)
66
+ trap 'rm -f "$COMPENDIUM"' EXIT
67
+ else
68
+ COMPENDIUM="$TARGET_COMPENDIUM"
69
+ fi
70
+
34
71
  # --- Field extractors ------------------------------------------------------
35
72
 
36
73
  # Read a frontmatter scalar field (single line `key: value`).
@@ -214,16 +251,48 @@ emit_entry() {
214
251
 
215
252
  # Collect + sort ADR files. README.md and any sibling -history.md / -summary.md
216
253
  # style files (future P194 etc.) are excluded.
217
- files=()
254
+ #
255
+ # Status sectioning (ADR-077 amended 2026-05-30): the compendium is split
256
+ # into two sections so the architect agent's routine load reads in-force
257
+ # decisions first and historical decisions second.
258
+ # - In-force (proposed + accepted): the current rules to follow.
259
+ # - Historical (superseded + rejected + deprecated): direction for what NOT
260
+ # to do — useful when reviewing a proposed change that re-treads a path
261
+ # that was tried and rejected, or that conflicts with a superseded
262
+ # decision's still-valid intent.
263
+ # Both sections sort by ID ascending; the status badge on each entry tells
264
+ # the agent which kind it is.
265
+ all_files=()
218
266
  while IFS= read -r f; do
219
- files+=("$f")
267
+ all_files+=("$f")
220
268
  done < <(find "$DECISIONS_DIR" -maxdepth 1 -type f -name '*.md' \
221
269
  ! -name 'README.md' \
222
270
  ! -name '*-history.md' \
223
271
  ! -name '*-summary.md' \
224
272
  2>/dev/null | sort)
225
273
 
226
- total=${#files[@]}
274
+ in_force_files=()
275
+ historical_files=()
276
+ for f in "${all_files[@]}"; do
277
+ s=$(get_frontmatter_field "$f" "status")
278
+ case "$s" in
279
+ proposed|accepted)
280
+ in_force_files+=("$f")
281
+ ;;
282
+ superseded|rejected|deprecated)
283
+ historical_files+=("$f")
284
+ ;;
285
+ *)
286
+ # Unknown status: surface as in-force so it isn't silently dropped;
287
+ # the badge will show "?" and a reviewer can correct the source.
288
+ in_force_files+=("$f")
289
+ ;;
290
+ esac
291
+ done
292
+
293
+ in_force_total=${#in_force_files[@]}
294
+ historical_total=${#historical_files[@]}
295
+ total=$((in_force_total + historical_total))
227
296
 
228
297
  # Header is deterministic — NO timestamp, NO date. The compendium must be
229
298
  # idempotent (same input bodies => byte-identical output) so the ADR-077
@@ -237,14 +306,58 @@ total=${#files[@]}
237
306
  echo ""
238
307
  echo "Compact rendered index of every ADR's chosen option, confirmation criteria, and relationship graph. **Authoritative substance lives in the per-ADR body** (\`<NNN>-<slug>.<status>.md\`); this compendium is a derived view for routine \`wr-architect:agent\` compliance review."
239
308
  echo ""
309
+ echo "**Two sections:**"
310
+ echo ""
311
+ echo "- **In-force decisions** (\`proposed\` + \`accepted\`) — the current rules to follow."
312
+ echo "- **Historical decisions** (\`superseded\` + \`rejected\` + \`deprecated\`) — direction for what NOT to do. Useful when reviewing a proposed change that re-treads a path already tried, or that conflicts with a superseded decision's still-valid intent. The status badge on each entry says which kind it is."
313
+ echo ""
240
314
  echo "For deep-dive — creating, evolving, ratifying, or contesting a decision — open the per-ADR file directly. \`/wr-architect:create-adr\`, \`/wr-architect:capture-adr\`, and \`/wr-architect:review-decisions\` all keep the full body in scope. Decision Drivers, Considered Options bodies, Pros and Cons, Consequences narrative, and Reassessment Criteria are intentionally NOT in this routine view — they live in the per-ADR body."
241
315
  echo ""
242
- echo "**Total ADRs:** ${total}"
316
+ echo "**Total ADRs:** ${total} (${in_force_total} in-force, ${historical_total} historical)"
243
317
  echo ""
244
318
  echo "---"
245
- for f in "${files[@]}"; do
319
+ echo ""
320
+ echo "## In-force decisions"
321
+ echo ""
322
+ echo "_${in_force_total} ADRs. These are the current rules. The architect agent reads this section first for routine compliance review._"
323
+ for f in "${in_force_files[@]}"; do
246
324
  emit_entry "$f"
247
325
  done
326
+ if [ "$historical_total" -gt 0 ]; then
327
+ echo ""
328
+ echo "---"
329
+ echo ""
330
+ echo "## Historical decisions"
331
+ echo ""
332
+ echo "_${historical_total} ADRs. These were tried and superseded, rejected, or deprecated. Read them as direction for what NOT to do, or to understand the lineage of an in-force decision. Do not enforce them as current rules._"
333
+ for f in "${historical_files[@]}"; do
334
+ emit_entry "$f"
335
+ done
336
+ fi
248
337
  } > "$COMPENDIUM"
249
338
 
250
- echo "generate-decisions-compendium: wrote $COMPENDIUM (${total} ADRs)" >&2
339
+ if [ "$CHECK_MODE" = "1" ]; then
340
+ # Check mode: diff temp against target. Idempotency contract holds only
341
+ # when both files exist; treat absence as "stale" (drift detected).
342
+ if [ ! -f "$TARGET_COMPENDIUM" ]; then
343
+ echo "generate-decisions-compendium: compendium MISSING — $TARGET_COMPENDIUM does not exist" >&2
344
+ echo " run: wr-architect-generate-decisions-compendium" >&2
345
+ exit 1
346
+ fi
347
+ if cmp -s "$COMPENDIUM" "$TARGET_COMPENDIUM"; then
348
+ echo "generate-decisions-compendium: compendium up-to-date (${total} ADRs — ${in_force_total} in-force, ${historical_total} historical)" >&2
349
+ exit 0
350
+ fi
351
+ {
352
+ echo "generate-decisions-compendium: compendium IS STALE relative to ADR bodies"
353
+ echo " expected (fresh generator output): $COMPENDIUM"
354
+ echo " actual (on disk): $TARGET_COMPENDIUM"
355
+ echo " run: wr-architect-generate-decisions-compendium && git add $TARGET_COMPENDIUM"
356
+ echo ""
357
+ echo "diff (first 40 lines):"
358
+ diff "$TARGET_COMPENDIUM" "$COMPENDIUM" 2>/dev/null | head -40
359
+ } >&2
360
+ exit 1
361
+ fi
362
+
363
+ echo "generate-decisions-compendium: wrote $COMPENDIUM (${total} ADRs total — ${in_force_total} in-force, ${historical_total} historical)" >&2
@@ -137,12 +137,22 @@ The numbered-options placeholder (`1. Option A (chosen) ...` + `2. (deferred ...
137
137
 
138
138
  Single `Write` to `docs/decisions/<NNN>-<kebab-title>.proposed.md`.
139
139
 
140
+ ### 4.5. Refresh the decisions compendium (ADR-077)
141
+
142
+ After the ADR skeleton lands, regenerate `docs/decisions/README.md` so the architect-agent routine load surface includes the new entry:
143
+
144
+ ```bash
145
+ wr-architect-generate-decisions-compendium
146
+ ```
147
+
148
+ The compendium is the architect agent's primary load surface per ADR-077; capture-adr owns keeping it fresh (skills + agent are PRIMARY; the `architect-compendium-refresh-discipline.sh` hook is the safety-net backstop). The next step stages both files together.
149
+
140
150
  ### 5. Commit per ADR-014 — single commit, no architect-review handoff
141
151
 
142
- **Stage list**: ONLY the new ADR file.
152
+ **Stage list**: the new ADR file AND the refreshed compendium (ADR-077 — both move in the same commit so the architect-compendium-refresh-discipline hook passes).
143
153
 
144
154
  ```bash
145
- git add docs/decisions/<NNN>-<kebab-title>.proposed.md
155
+ git add docs/decisions/<NNN>-<kebab-title>.proposed.md docs/decisions/README.md
146
156
  ```
147
157
 
148
158
  Satisfy the commit gate per ADR-014 — same two-path pattern as `manage-problem` Step 11 / `capture-problem` Step 6:
@@ -207,6 +207,15 @@ oversight-date: YYYY-MM-DD # today
207
207
 
208
208
  This is the load-bearing born-confirmed gate: an ADR recorded through create-adr enters the world already human-oversighted (it does not appear in `/wr-architect:review-decisions`' unoversighted set). Do NOT write the marker if the user has not confirmed (rejected / still-iterating ADRs stay unmarked). The marker is orthogonal to `status:` — a `proposed` ADR can be `human-oversight: confirmed`.
209
209
 
210
+ **Refresh the decisions compendium (ADR-077).** After the ADR file is written and any born-confirmed marker is applied, regenerate `docs/decisions/README.md` so the architect-agent routine load surface includes the new entry. Run:
211
+
212
+ ```bash
213
+ wr-architect-generate-decisions-compendium
214
+ git add docs/decisions/README.md
215
+ ```
216
+
217
+ The compendium is the architect agent's primary load surface per ADR-077; skills own keeping it fresh. The `architect-compendium-refresh-discipline.sh` PreToolUse hook is the safety-net backstop — it will DENY a commit that stages the new `docs/decisions/<NNN>-*.md` without a matching `docs/decisions/README.md`. Regenerating here makes that hook a no-op on the happy path.
218
+
210
219
  ### 6. Handle supersession (if applicable)
211
220
 
212
221
  If the user mentions this decision replaces an existing one: