@windyroad/architect 0.11.0 → 0.12.0

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.11.0"
126
+ "version": "0.12.0"
127
127
  }
package/agents/agent.md CHANGED
@@ -159,12 +159,13 @@ Do NOT emit Needs Direction for the "obvious choice" / only-one-viable-option ca
159
159
 
160
160
  ### When to flag [Unratified Dependency] (ADR-074 (Confirm a decision's substance before building dependent work) surface 3)
161
161
 
162
- When the change or plan under review **explicitly cites or implements** a specific ADR (e.g. the diff/prose says "per ADR-072", a `Refs: ADR-NNN`, or it is authoring the work the ADR governs), check whether that ADR has been **ratified** before letting the change stand. You have Read/Glob/Grep (no Bash), so perform the **read-only equivalent** of `packages/architect/scripts/is-decision-unconfirmed.sh` — mirror BOTH halves of its "unconfirmed" definition:
162
+ When the change or plan under review **explicitly cites or implements** a specific ADR (e.g. the diff/prose says "per ADR-072", a `Refs: ADR-NNN`, or it is authoring the work the ADR governs), check whether that ADR has been **ratified** before letting the change stand. You have Read/Glob/Grep (no Bash), so perform the **read-only equivalent** of `packages/architect/scripts/is-decision-unconfirmed.sh` — mirror ALL THREE halves of its "unconfirmed" definition:
163
163
 
164
164
  1. **Frontmatter-scoped marker check.** Read the cited ADR file and inspect ONLY its YAML frontmatter (the block between the leading `---` and the next `---`). The ADR is **ratified** iff that frontmatter contains a line matching `human-oversight: confirmed` — case-insensitive, tolerating trailing whitespace (the canonical predicate greps `-iE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$'`). A body mention of that string does NOT count — it must be in frontmatter.
165
165
  2. **Superseded skip.** A `*.superseded.md` ADR is retired — treat it as ratified-equivalent (do NOT flag); a newer ADR replaced it.
166
+ 3. **Rejected-pending-supersede skip** (ADR-066 amendment per P316). An ADR whose frontmatter carries BOTH `human-oversight: rejected-pending-supersede` AND `supersede-ticket: P<NNN>` is ratified-equivalent — the user has explicitly rejected the ADR and pinned a supersede in flight. Treat as ratified (do NOT flag). The marker alone (without the `supersede-ticket:` scalar) does NOT skip — it is malformed and surfaces as unratified so the un-tracked case doesn't silently rot.
166
167
 
167
- Emit **ISSUES FOUND / [Unratified Dependency]** only when the cited ADR's frontmatter lacks the marker AND it is not superseded — action: "ratify ADR-NNN via `/wr-architect:review-decisions` before this lands."
168
+ Emit **ISSUES FOUND / [Unratified Dependency]** only when the cited ADR's frontmatter lacks the `confirmed` marker AND it is not superseded AND it does not carry the rejected-pending-supersede + supersede-ticket pair — action: "ratify ADR-NNN via `/wr-architect:review-decisions` before this lands."
168
169
 
169
170
  **Key the flag on the oversight marker, NEVER on `status:`.** `status: proposed`/`accepted` and `human-oversight:` are orthogonal axes (ADR-066). Building on a **ratified** ADR is fine even when its `status` is still `proposed` — do NOT flag it. Only the *unratified* (marker-absent, non-superseded) case flags. In steady state almost every ADR is ratified (born-confirmed via `create-adr` + the review-decisions drain), so this fires on essentially nothing — do not over-scan or flag transitive/ambient dependence on governed code, only an explicit cite/implement of a specific unratified ADR (the inverse-P078 / P132 over-fire guard).
170
171
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/architect",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "bin": {
6
6
  "windyroad-architect": "./bin/install.mjs"
@@ -45,7 +45,18 @@ for f in "$DECISIONS_DIR"/*.md "$DECISIONS_DIR"/*/*.md; do
45
45
  { print }
46
46
  ' "$f")"
47
47
 
48
- if ! printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$'; then
49
- echo "$f"
48
+ if printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$'; then
49
+ continue
50
50
  fi
51
+
52
+ # ADR-066 amendment (P316): rejected-pending-supersede is a third oversight
53
+ # value. Exclude only when BOTH the marker AND a supersede-ticket: P<NNN>
54
+ # scalar are present in frontmatter — a marker without the ticket is
55
+ # malformed and still surfaces so the drain catches the un-tracked case.
56
+ if printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*rejected-pending-supersede[[:space:]]*$' \
57
+ && printf '%s\n' "$fm" | grep -qiE '^supersede-ticket:[[:space:]]*P[0-9]+[[:space:]]*$'; then
58
+ continue
59
+ fi
60
+
61
+ echo "$f"
51
62
  done | sort
@@ -195,7 +195,7 @@ truncate_with_ellipsis() {
195
195
 
196
196
  emit_entry() {
197
197
  local file="$1"
198
- local id title status oversight superseded
198
+ local id title status oversight superseded supersede_ticket
199
199
  local chosen drivers confirmation related
200
200
 
201
201
  id=$(basename "$file" | grep -oE '^[0-9]+')
@@ -203,6 +203,11 @@ emit_entry() {
203
203
  status=$(get_frontmatter_field "$file" "status")
204
204
  oversight=$(get_frontmatter_field "$file" "human-oversight")
205
205
  superseded=$(get_frontmatter_field "$file" "supersedes")
206
+ # ADR-066 amendment (P316): when the oversight value is
207
+ # `rejected-pending-supersede`, surface the tracking ticket parenthetically
208
+ # so the compendium badge shows both the disposition AND the supersede in
209
+ # flight without a per-ADR body read.
210
+ supersede_ticket=$(get_frontmatter_field "$file" "supersede-ticket")
206
211
 
207
212
  # Chosen-option line — truncate to a comfortable summary length.
208
213
  chosen=$(get_chosen "$file" | strip_links | oneline)
@@ -229,7 +234,11 @@ emit_entry() {
229
234
  # Status / oversight / supersession badges on one compact line.
230
235
  local badges="**Status:** ${status:-?}"
231
236
  if [ -n "$oversight" ]; then
232
- badges="${badges} | **Oversight:** ${oversight}"
237
+ if [ "$oversight" = "rejected-pending-supersede" ] && [ -n "$supersede_ticket" ]; then
238
+ badges="${badges} | **Oversight:** ${oversight} (${supersede_ticket})"
239
+ else
240
+ badges="${badges} | **Oversight:** ${oversight}"
241
+ fi
233
242
  fi
234
243
  if [ -n "$superseded" ] && [ "$superseded" != "[]" ]; then
235
244
  badges="${badges} | **Supersedes:** ${superseded}"
@@ -76,6 +76,16 @@ if printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:spa
76
76
  exit 1 # confirmed — OK to build on
77
77
  fi
78
78
 
79
+ # ADR-066 amendment (P316): rejected-pending-supersede with a tracked
80
+ # supersede-ticket is "ratified-equivalent" for the build-upon guard — the
81
+ # user has explicitly rejected the ADR and pinned a supersede in flight, so
82
+ # the [Unratified Dependency] flag must NOT re-fire on the rejected ADR.
83
+ # Marker without ticket is malformed and still fires (defensive).
84
+ if printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*rejected-pending-supersede[[:space:]]*$' \
85
+ && printf '%s\n' "$fm" | grep -qiE '^supersede-ticket:[[:space:]]*P[0-9]+[[:space:]]*$'; then
86
+ exit 1
87
+ fi
88
+
79
89
  # Unconfirmed — the build-upon guard SHOULD fire. Name the file for the guard.
80
90
  echo "$file"
81
91
  exit 0
@@ -97,3 +97,43 @@ mk() { # mk <filename> <frontmatter-extra-lines...>
97
97
  [ "$status" -eq 0 ]
98
98
  [ -z "$output" ]
99
99
  }
100
+
101
+ # ADR-066 amendment (P316): rejected-pending-supersede is a third oversight
102
+ # value alongside `confirmed` and absent. It exists so the drain stops crying
103
+ # wolf on ADRs the user has explicitly rejected with a tracked supersede
104
+ # ticket (otherwise they re-surface every drain until the supersede ADR
105
+ # lands). Exclusion is conditional on BOTH the marker AND a supersede-ticket
106
+ # scalar being present — a marker without a ticket is malformed and still
107
+ # surfaces (defensive, JTBD-201/202 audit-trail guard).
108
+
109
+ @test "rejected-pending-supersede WITH supersede-ticket is excluded" {
110
+ mk "020-rejected-tracked.proposed.md" \
111
+ "human-oversight: rejected-pending-supersede" \
112
+ "supersede-ticket: P297"
113
+ run bash "$SCRIPT" "$DIR/docs/decisions"
114
+ [ "$status" -eq 0 ]
115
+ [[ "$output" != *"020-rejected-tracked.proposed.md"* ]]
116
+ }
117
+
118
+ @test "rejected-pending-supersede WITHOUT supersede-ticket still surfaces (defensive)" {
119
+ mk "021-rejected-untracked.proposed.md" \
120
+ "human-oversight: rejected-pending-supersede"
121
+ run bash "$SCRIPT" "$DIR/docs/decisions"
122
+ [ "$status" -eq 0 ]
123
+ [[ "$output" == *"021-rejected-untracked.proposed.md"* ]]
124
+ }
125
+
126
+ @test "supersede-ticket alone (without the rejected marker) does NOT exclude" {
127
+ mk "022-ticket-only.proposed.md" "supersede-ticket: P297"
128
+ run bash "$SCRIPT" "$DIR/docs/decisions"
129
+ [ "$status" -eq 0 ]
130
+ [[ "$output" == *"022-ticket-only.proposed.md"* ]]
131
+ }
132
+
133
+ @test "rejected-pending-supersede match tolerates trailing whitespace" {
134
+ mk "023-spacey.proposed.md" \
135
+ "human-oversight: rejected-pending-supersede " \
136
+ "supersede-ticket: P297 "
137
+ run bash "$SCRIPT" "$DIR/docs/decisions"
138
+ [[ "$output" != *"023-spacey.proposed.md"* ]]
139
+ }
@@ -106,3 +106,38 @@ mk() {
106
106
  [[ "$detect_out" != *"111-confirmed.proposed.md"* ]]
107
107
  run bash "$SCRIPT" "ADR-111" "$DIR/docs/decisions"; [ "$status" -eq 1 ]
108
108
  }
109
+
110
+ # ADR-066 amendment (P316): mirror the rejected-pending-supersede exclusion.
111
+ # The build-upon guard must NOT fire on an ADR the user explicitly rejected
112
+ # with a tracked supersede ticket — otherwise the [Unratified Dependency]
113
+ # verdict re-triggers on every iteration that touches the rejected ADR.
114
+
115
+ @test "rejected-pending-supersede WITH supersede-ticket does NOT fire the guard (exit 1)" {
116
+ mk "120-rejected-tracked.proposed.md" "proposed" \
117
+ "human-oversight: rejected-pending-supersede" \
118
+ "supersede-ticket: P297"
119
+ run bash "$SCRIPT" "ADR-120" "$DIR/docs/decisions"
120
+ [ "$status" -eq 1 ]
121
+ [ -z "$output" ]
122
+ }
123
+
124
+ @test "rejected-pending-supersede WITHOUT supersede-ticket DOES fire the guard (exit 0)" {
125
+ mk "121-rejected-untracked.proposed.md" "proposed" \
126
+ "human-oversight: rejected-pending-supersede"
127
+ run bash "$SCRIPT" "ADR-121" "$DIR/docs/decisions"
128
+ [ "$status" -eq 0 ]
129
+ [[ "$output" == *"121-rejected-untracked.proposed.md"* ]]
130
+ }
131
+
132
+ @test "agrees with detect-unoversighted on rejected-pending-supersede (sync guard)" {
133
+ mk "130-rejected.proposed.md" "proposed" \
134
+ "human-oversight: rejected-pending-supersede" \
135
+ "supersede-ticket: P298"
136
+ mk "131-rejected-untracked.proposed.md" "proposed" \
137
+ "human-oversight: rejected-pending-supersede"
138
+ detect_out="$(bash "$DETECT" "$DIR/docs/decisions")"
139
+ [[ "$detect_out" != *"130-rejected.proposed.md"* ]]
140
+ run bash "$SCRIPT" "ADR-130" "$DIR/docs/decisions"; [ "$status" -eq 1 ]
141
+ [[ "$detect_out" == *"131-rejected-untracked.proposed.md"* ]]
142
+ run bash "$SCRIPT" "ADR-131" "$DIR/docs/decisions"; [ "$status" -eq 0 ]
143
+ }
@@ -44,7 +44,7 @@ For each ADR in the ordered queue, surface the decision as an `AskUserQuestion`
44
44
  - **Options** (per ADR):
45
45
  - **Confirm** — the recorded decision is correct; write the marker.
46
46
  - **Amend** — the decision is mostly right but needs a change; capture the change, apply it to the ADR body, then write the marker.
47
- - **Reject / supersede** — the auto-made pick is wrong; do NOT write the marker. Note the rework needed (a follow-up `/wr-architect:create-adr` supersede, or a problem ticket).
47
+ - **Reject / supersede** — the auto-made pick is wrong; capture the supersede ticket (see Step 4) and write the **rejected-pending-supersede** marker so the drain stops re-asking.
48
48
  - **Defer** — skip this sitting; leave unoversighted for a later run.
49
49
 
50
50
  This is a genuine human-decision surface (the whole point of P283) — `AskUserQuestion` is correct here and is NOT over-asking. Do not auto-confirm; do not prose-ask.
@@ -52,7 +52,10 @@ This is a genuine human-decision surface (the whole point of P283) — `AskUserQ
52
52
  ### Step 4: Apply the outcome
53
53
 
54
54
  - **Confirm / Amend**: write `human-oversight: confirmed` + `oversight-date: <today, YYYY-MM-DD>` into the ADR's frontmatter (insert after the `date:` line if absent; never duplicate). For Amend, apply the directed body change first. Both edits go through the standard architect / JTBD edit gate per ADR-014.
55
- - **Reject / supersede**: leave the marker absent. Record the rework (follow-up create-adr supersede or `/wr-itil:capture-problem`).
55
+ - **Reject / supersede** (ADR-066 amendment per P316):
56
+ 1. Capture the supersede ticket via a follow-up `AskUserQuestion`: "Which problem ticket tracks the supersede?" — options: existing `P<NNN>` IDs surfaced from `docs/problems/`, **Capture a new ticket** (delegate to `/wr-itil:capture-problem`), or **Defer (leave un-tracked for now)**.
57
+ 2. If a ticket ID is captured, write `human-oversight: rejected-pending-supersede` + `supersede-ticket: P<NNN>` into the ADR's frontmatter. The detector excludes ADRs carrying both, so the drain stops re-asking until either the supersede ADR lands (status flips to `superseded`) or the rejection is revisited.
58
+ 3. If the user defers ticket capture, leave the marker absent — the ADR re-surfaces next drain (the un-tracked case is intentionally re-asked so it doesn't silently rot).
56
59
  - **Defer**: no write.
57
60
 
58
61
  ### Step 5: Commit + report
@@ -61,7 +64,7 @@ Commit the confirmed/amended ADRs per ADR-014 (one commit for the sitting's drai
61
64
 
62
65
  ## Notes
63
66
 
64
- - **Never re-ask** — a confirmed ADR carries the marker permanently and is excluded from future runs (ADR-009 never-re-ask principle). The marker is write-once **except** when an ADR is materially amended after confirmation (the Decision Outcome is rewritten) — a supersede/amend clears it for re-confirmation per ADR-066 Reassessment.
67
+ - **Never re-ask** — a confirmed ADR carries the marker permanently and is excluded from future runs (ADR-009 never-re-ask principle). The same write-once-permanence applies to the `rejected-pending-supersede` value (P316 amendment): once the user rejects with a tracked ticket, the drain stops asking. The marker is write-once **except** when an ADR is materially amended after confirmation (the Decision Outcome is rewritten) — a supersede/amend clears it for re-confirmation per ADR-066 Reassessment. When the supersede ADR eventually lands and the original flips to `*.superseded.md`, the existing superseded-status skip takes over; the `rejected-pending-supersede` lines become historical residue (no active clearance required).
65
68
  - **AFK** — this skill is interactive by construction (the confirm IS the human decision). It is not dispatched inside AFK iteration subprocesses; the session-start nudge self-suppresses there (`WR_SUPPRESS_OVERSIGHT_NUDGE=1`) so the drain is never half-run by an absent user.
66
69
  - **Born-confirmed going forward** — `/wr-architect:create-adr` writes the marker at its Step 5 confirm, so new ADRs enter the set already oversighted and the unoversighted count only shrinks.
67
70