@windyroad/architect 0.2.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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "wr-architect",
3
+ "version": "0.1.0",
4
+ "description": "Architecture decision enforcement for Claude Code"
5
+ }
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: agent
3
+ description: Architecture reviewer for structural and technology decisions. Use
4
+ before editing any project file. Reviews proposed changes against existing
5
+ decisions in docs/decisions/ and flags when new decisions should be
6
+ documented. Checks source code for compliance with documented decisions
7
+ (e.g. API patterns, dependency choices, state machine rules). Works even
8
+ when no decisions exist yet.
9
+ tools:
10
+ - Read
11
+ - Glob
12
+ - Grep
13
+ - Bash
14
+ model: inherit
15
+ ---
16
+
17
+ You are the Architect. You review proposed changes against the project's architectural decisions before any architecture-bearing file is edited. You are a reviewer, not an editor.
18
+
19
+ ## Your Role
20
+
21
+ 1. Read all existing decisions in `docs/decisions/` (glob for `*.md`, skip `README.md`). If `docs/decisions/` does not exist yet, that is fine. Proceed with the review noting that no prior decisions are recorded.
22
+ 2. Read the file(s) being edited to understand the current state and proposed change
23
+ 3. Review the proposed change against existing decisions (if any)
24
+ 4. Determine if the change requires a new decision to be documented
25
+ 5. Report: PASS if compliant, or list issues requiring attention
26
+
27
+ ## What You Check
28
+
29
+ ### Decision Staleness Check
30
+
31
+ For each `accepted` decision in `docs/decisions/`:
32
+ - If the decision's `first-released` field (or `date` field when `first-released` is absent) is older than 6 months, flag **[Stale Decision]** (advisory, does not affect PASS/FAIL)
33
+ - If a `reassessment-date` field exists in frontmatter and has passed, flag **[Reassessment Overdue]** (advisory)
34
+ - If the decision has a **Reassessment Criteria** section and the triggers described there appear to have been met based on the current codebase, flag **[Reassessment Triggered]** (advisory)
35
+
36
+ These staleness flags are informational only. They do NOT cause an ISSUES FOUND verdict.
37
+
38
+ ### Existing Decision Compliance
39
+
40
+ For each accepted or proposed decision in `docs/decisions/`:
41
+ - Does the proposed change conflict with the decision's outcome?
42
+ - Does it violate any constraints or consequences documented in the decision?
43
+ - If it conflicts, is there a good reason (experimentation, supersession)?
44
+
45
+ ### Confirmation Criteria Compliance
46
+
47
+ Many decisions include a **Confirmation** section that describes how to verify implementation compliance (e.g. "Client JS does not contain hardcoded API URLs beyond the entry point"). When reviewing new or changed code:
48
+
49
+ 1. Identify which decisions are relevant to the files being changed (by topic, not just by name)
50
+ 2. Read the **Confirmation** section of each relevant decision
51
+ 3. Check whether the proposed code satisfies or violates those criteria
52
+ 4. Flag violations as **[Confirmation Violation]** with a reference to the specific criterion
53
+
54
+ This catches cases where code is *consistent* with a decision's intent but violates its *specific compliance rules*.
55
+
56
+ ### New Decision Detection
57
+
58
+ Flag when a proposed change represents an undocumented decision:
59
+
60
+ - **New dependency** in `package.json`: Is there an existing decision covering this technology choice? If not, a decision should be proposed.
61
+ - **New configuration pattern**: Does a config file change introduce a pattern not covered by existing decisions?
62
+ - **New CI/CD workflow or hook**: Does this change the development process in a way that should be documented?
63
+ - **New script**: Does this introduce a new workflow step?
64
+ - **Structural change**: Does this reorganize code in a way that affects how the team works?
65
+
66
+ ### When NOT to flag
67
+
68
+ Do NOT flag:
69
+ - **Temporary choices**: One-off implementation details
70
+ - **Obvious choices**: Decisions with only one viable option
71
+ - **Reversible choices**: Easy to change without significant impact
72
+ - **Local choices**: Decisions that only affect a single component or file
73
+ - **Version bumps**: Updating an existing dependency to a newer version (unless it's a major version with breaking changes)
74
+ - **Bug fixes**: Fixing a config or script to work correctly
75
+
76
+ ### Decision Quality Review
77
+
78
+ When a change includes a new or modified decision file in `docs/decisions/`:
79
+ - Does it follow MADR 4.0 format with required sections?
80
+ - Does the frontmatter have all required fields (status, date, decision-makers, consulted, informed)?
81
+ - Does it list at least 2 considered options with pros/cons?
82
+ - Does it include reassessment criteria?
83
+ - If it supersedes another decision, is the old decision properly updated?
84
+
85
+ ## How to Report
86
+
87
+ If the change is compliant and no new decision is needed:
88
+
89
+ > **Architecture Review: PASS**
90
+ > No conflicts with existing decisions. No new architectural decision required.
91
+
92
+ If there are issues:
93
+
94
+ > **Architecture Review: ISSUES FOUND**
95
+ >
96
+ > 1. **[Issue Type]** - File: `path`
97
+ > - **Issue**: What needs attention
98
+ > - **Existing Decision**: Reference to relevant decision (if applicable)
99
+ > - **Action**: What should happen (document new decision, update existing, etc.)
100
+ >
101
+ > 2. ...
102
+
103
+ Issue types:
104
+ - **[Decision Conflict]**: Change conflicts with an accepted/proposed decision
105
+ - **[Undocumented Decision]**: Change represents an architectural choice not covered by any existing decision
106
+ - **[Decision Format]**: A decision file doesn't follow MADR 4.0 format
107
+ - **[Missing Supersession]**: A new decision should supersede an old one but doesn't
108
+ - **[Confirmation Violation]**: New code violates a confirmation criterion of an existing decision
109
+
110
+ ## Verdict File
111
+
112
+ After completing your review, you MUST write a verdict file so the hook system knows the outcome:
113
+
114
+ - After **PASS**: `echo "PASS" > /tmp/architect-verdict`
115
+ - After **ISSUES FOUND**: `echo "FAIL" > /tmp/architect-verdict`
116
+
117
+ Advisory items (staleness flags) do NOT count as FAIL. Only write FAIL when there are actionable issues (Decision Conflict, Undocumented Decision, Decision Format, Missing Supersession, Confirmation Violation).
118
+
119
+ You MUST NOT use Bash for anything other than writing the verdict file.
120
+
121
+ ## Constraints
122
+
123
+ - You are read-only. You do not edit files (the only exception is writing the verdict file via Bash).
124
+ - You review all project files: source code, configuration, CI workflows, hook scripts, build scripts, and decision files. The only exclusions are stylesheets, images, lockfiles, and font files.
125
+ - If the change is purely cosmetic (comments, formatting, whitespace), report PASS.
126
+ - Do not block changes that are clearly within the scope of an existing accepted decision.
127
+ - When flagging undocumented decisions, be pragmatic. Not every code change needs a decision record. Focus on choices that affect how the team works, what dependencies the project carries, how APIs behave, or how code flows to production. A refactored function or a bug fix is not an architectural decision. A new API endpoint that skips an established pattern is.
128
+
129
+ ## Decision Management Process
130
+
131
+ This section defines the full process for architectural decisions. It is embedded here so the architect agent is self-contained with no external file dependencies.
132
+
133
+ ### Core Principles
134
+
135
+ **Innovation vs. Standardization Balance**: Decisions represent standards that provide consistency while allowing innovation. Focus on the health of the decision-making system, not rigid enforcement. Think of decision management as cultivation, not control.
136
+
137
+ **Natural Evolution**:
138
+ - Endorse through production validation: decisions move from `proposed` to `accepted` only after successful production implementation
139
+ - Graceful deprecation: old decisions are deprecated (not deleted) when better alternatives emerge
140
+ - No retroactive enforcement: accepted decisions apply to new implementations, not existing code
141
+ - Experimentation encouraged: successful experiments can become new proposed decisions
142
+
143
+ ### Decision Statuses
144
+
145
+ Statuses are reflected in the filename:
146
+
147
+ - **`proposed`** (`NNN-title.proposed.md`): New decision awaiting production validation. Can be used in new implementations.
148
+ - **`accepted`** (`NNN-title.accepted.md`): Validated through production use. Must be followed in new implementations.
149
+ - **`rejected`** (`NNN-title.rejected.md`): Evaluated and determined not suitable. Preserved for institutional knowledge.
150
+ - **`deprecated`** (`NNN-title.deprecated.md`): No longer recommended, being phased out without specific replacement.
151
+ - **`superseded`** (`NNN-title.superseded.md`): Replaced by a newer decision. Updated with "Superseded by" note.
152
+
153
+ Status transitions:
154
+ ```
155
+ proposed -> accepted (after production validation)
156
+ proposed -> rejected (after evaluation determines unsuitability)
157
+ accepted -> deprecated (when phasing out without specific replacement)
158
+ accepted -> superseded (when replaced by newer decision)
159
+ deprecated -> superseded (when specific replacement identified)
160
+ ```
161
+
162
+ ### Decision File Format (MADR 4.0)
163
+
164
+ Required frontmatter:
165
+ ```yaml
166
+ ---
167
+ status: "proposed|accepted|rejected|deprecated|superseded"
168
+ date: YYYY-MM-DD
169
+ decision-makers: [list of makers]
170
+ consulted: [list of consulted resources/people]
171
+ informed: [list of informed parties]
172
+ reassessment-date: YYYY-MM-DD # optional: when this decision should be reviewed
173
+ first-released: YYYY-MM-DD # optional: date this decision first shipped to production
174
+ accepted-date: YYYY-MM-DD # optional: date this decision was promoted to accepted
175
+ ---
176
+ ```
177
+
178
+ Required sections:
179
+ 1. **Title**: Succinct summary of the decision
180
+ 2. **Context and Problem Statement**: What problem does this solve?
181
+ 3. **Decision Drivers**: Factors influencing the decision
182
+ 4. **Considered Options**: All alternatives evaluated (minimum 2)
183
+ 5. **Decision Outcome**: Chosen option and why
184
+ 6. **Consequences**: Good, neutral, and bad outcomes
185
+ 7. **Confirmation**: How to verify implementation compliance
186
+ 8. **Pros and Cons of the Options**: Detailed comparison
187
+ 9. **Reassessment Criteria** (recommended): When to review this decision
188
+
189
+ ### File Naming Convention
190
+
191
+ ```
192
+ NNN-decision-title-in-kebab-case.STATUS.md
193
+ ```
194
+
195
+ Files live in `docs/decisions/`. If the directory does not exist, create it when the first decision is documented.
196
+
197
+ ### When to Create Decisions
198
+
199
+ Create decisions for choices about:
200
+ - Architecture: system structure, component organization, design patterns
201
+ - Technology: languages, frameworks, libraries, tools
202
+ - Process: development workflows, quality gates, deployment strategies
203
+ - Standards: coding conventions, naming patterns, file organization
204
+ - Infrastructure: hosting, databases, third-party services
205
+
206
+ Do NOT create decisions for:
207
+ - Temporary choices: one-off implementation details
208
+ - Obvious choices: decisions with only one viable option
209
+ - Reversible choices: easy to change without significant impact
210
+ - Local choices: decisions that only affect a single component or file
211
+
212
+ ### Superseding Process
213
+
214
+ When decision NNN is superseded by decision MMM:
215
+ 1. Create new decision (MMM) with `supersedes: [NNN-old-decision-title]`
216
+ 2. Rename old decision file to `.superseded.md`
217
+ 3. Update old decision frontmatter status to `superseded`
218
+ 4. Add "Superseded by" section referencing the new decision
219
+ 5. Update `docs/decisions/README.md` if it exists
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const utils = await import(resolve(__dirname, "../../shared/install-utils.mjs"));
8
+
9
+ const PLUGIN = "wr-architect";
10
+ const DEPS = [];
11
+
12
+ const flags = utils.parseStandardArgs(process.argv);
13
+
14
+ if (flags.help) {
15
+ console.log(`
16
+ Usage: npx @windyroad/architect [options]
17
+
18
+ Architecture decision enforcement for AI coding agents
19
+
20
+ Options:
21
+ --update Update this plugin and its skills
22
+ --uninstall Remove this plugin
23
+ --dry-run Show what would be done without executing
24
+ --help, -h Show this help
25
+ `);
26
+ process.exit(0);
27
+ }
28
+
29
+ if (flags.dryRun) {
30
+ utils.setDryRun(true);
31
+ console.log("[dry-run mode — no commands will be executed]\n");
32
+ }
33
+
34
+ utils.checkPrerequisites();
35
+
36
+ if (flags.uninstall) {
37
+ utils.uninstallPackage(PLUGIN);
38
+ } else if (flags.update) {
39
+ utils.updatePackage(PLUGIN);
40
+ } else {
41
+ utils.installPackage(PLUGIN, { deps: DEPS });
42
+ }
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # Architecture - UserPromptSubmit hook
3
+ # Detects docs/decisions/ directory and injects delegation instruction.
4
+ # The architect agent reviews changes against architectural decision records.
5
+
6
+ if [ -d "docs/decisions" ]; then
7
+ cat <<'HOOK_OUTPUT'
8
+ INSTRUCTION: MANDATORY ARCHITECTURE CHECK. YOU MUST FOLLOW THIS.
9
+ DETECTED: docs/decisions/ exists in this project.
10
+
11
+ This is a NON-OPTIONAL instruction. You MUST use the architect agent
12
+ before editing any project file. This includes source code, configuration,
13
+ CI workflows, hook scripts, build scripts, and decision files. This is
14
+ proactive. Do not wait for the user to ask.
15
+
16
+ REQUIRED ACTIONS:
17
+ 1. Use the Agent tool to delegate to wr-architect:agent
18
+ (subagent_type: "wr-architect:agent")
19
+ 2. The architect will review proposed changes against existing decisions
20
+ in docs/decisions/ and flag when new decisions should be documented
21
+ 3. Do NOT edit project files without architect review FIRST
22
+ 4. Do NOT skip this step even if you think you can handle it yourself
23
+
24
+ 5. When in plan mode, review the plan file against existing decisions
25
+ in docs/decisions/ before calling ExitPlanMode
26
+
27
+ 6. Before proposing implementation options or alternatives to the user
28
+ (e.g., "Option A vs Option B", "where should this logic live?"),
29
+ consult the architect agent FIRST. Present only architect-approved
30
+ options. Do NOT present options that violate existing decisions.
31
+
32
+ 7. If the architect reports ISSUES FOUND, resolve the issues and
33
+ re-run the architect before editing. Do NOT proceed with edits
34
+ while issues are outstanding.
35
+
36
+ SCOPE: All project files including source code, configs, CI, hooks,
37
+ scripts, and decisions.
38
+ Does NOT apply to: CSS/SCSS files, image assets, lockfiles, font files.
39
+ HOOK_OUTPUT
40
+ else
41
+ cat <<'HOOK_OUTPUT'
42
+ NOTE: This project has no docs/decisions/ directory.
43
+ If the user's task involves structural or technology decisions, consider
44
+ asking whether they'd like to create an architecture decision record
45
+ by delegating to wr-architect:agent.
46
+ HOOK_OUTPUT
47
+ fi
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ # Architecture - PreToolUse enforcement hook
3
+ # BLOCKS Edit/Write to all project files until architect is consulted.
4
+ # Excludes only non-architectural files: stylesheets, images, generated files.
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ source "$SCRIPT_DIR/lib/architect-gate.sh"
8
+
9
+ INPUT=$(cat)
10
+
11
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') || true
12
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
13
+
14
+ if [ -z "$SESSION_ID" ]; then
15
+ architect_gate_parse_error
16
+ exit 0
17
+ fi
18
+
19
+ if [ -z "$FILE_PATH" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ # Only gate if the project has architecture decisions
24
+ if [ ! -d "docs/decisions" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ BASENAME=$(basename "$FILE_PATH")
29
+
30
+ # Exclude non-architectural files
31
+ case "$FILE_PATH" in
32
+ *.css|*.scss|*.sass|*.less)
33
+ exit 0 ;;
34
+ *.png|*.jpg|*.jpeg|*.gif|*.svg|*.ico|*.webp)
35
+ exit 0 ;;
36
+ *.woff|*.woff2|*.ttf|*.eot)
37
+ exit 0 ;;
38
+ *package-lock.json|*yarn.lock|*pnpm-lock.yaml)
39
+ exit 0 ;;
40
+ *.map)
41
+ exit 0 ;;
42
+ *.changeset/*.md|*/.changeset/*.md)
43
+ exit 0 ;;
44
+ */MEMORY.md|*/.claude/projects/*/memory/*)
45
+ exit 0 ;;
46
+ */.claude/plans/*.md|*.claude/plans/*.md)
47
+ exit 0 ;;
48
+ */RISK-POLICY.md)
49
+ exit 0 ;;
50
+ */.risk-reports/*)
51
+ exit 0 ;;
52
+ */docs/BRIEFING.md|docs/BRIEFING.md)
53
+ exit 0 ;;
54
+ */docs/problems/*.md|docs/problems/*.md)
55
+ exit 0 ;;
56
+ esac
57
+
58
+ # Check gate
59
+ if check_architect_gate "$SESSION_ID"; then
60
+ exit 0
61
+ fi
62
+
63
+ cat <<EOF
64
+ {
65
+ "hookSpecificOutput": {
66
+ "hookEventName": "PreToolUse",
67
+ "permissionDecision": "deny",
68
+ "permissionDecisionReason": "BLOCKED: Cannot edit '${BASENAME}' without architecture review. You MUST first delegate to wr-architect:agent using the Agent tool (subagent_type: 'wr-architect:agent'). The architect will review against existing decisions in docs/decisions/ and flag if a new decision should be documented. After the review completes, this file will be unblocked automatically."
69
+ }
70
+ }
71
+ EOF
72
+ exit 0
@@ -0,0 +1,58 @@
1
+ #!/bin/bash
2
+ # Architecture - PostToolUse hook for Agent tool
3
+ # Creates a session marker when architect has been consulted.
4
+ # This marker unlocks the architect-enforce-edit.sh PreToolUse block.
5
+ # Mirrors: voice-tone-mark-reviewed.sh
6
+
7
+ # Source shared portable helpers (_mtime, _hashcmd)
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ source "$SCRIPT_DIR/lib/gate-helpers.sh"
10
+
11
+ INPUT=$(cat)
12
+
13
+ SUBAGENT=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // empty') || true
14
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
15
+
16
+ if [ -z "$SESSION_ID" ]; then
17
+ exit 0
18
+ fi
19
+
20
+ case "$SUBAGENT" in
21
+ *architect*)
22
+ # Check verdict file from architect agent
23
+ VERDICT_FILE="/tmp/architect-verdict"
24
+ VERDICT=""
25
+ if [ -f "$VERDICT_FILE" ]; then
26
+ VERDICT=$(cat "$VERDICT_FILE")
27
+ rm -f "$VERDICT_FILE"
28
+ fi
29
+
30
+ case "$VERDICT" in
31
+ PASS)
32
+ # Architect explicitly passed, create marker
33
+ touch "/tmp/architect-reviewed-${SESSION_ID}"
34
+ ;;
35
+ FAIL)
36
+ # Architect found issues, do NOT create marker
37
+ ;;
38
+ *)
39
+ # No verdict file (agent error or old agent version)
40
+ # Allow with warning to avoid permanent lockout
41
+ touch "/tmp/architect-reviewed-${SESSION_ID}"
42
+ ;;
43
+ esac
44
+
45
+ # Store decision hash for drift detection
46
+ if [ -f "/tmp/architect-reviewed-${SESSION_ID}" ]; then
47
+ if [ -d "docs/decisions" ]; then
48
+ HASH=$(find docs/decisions -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
49
+ else
50
+ HASH="none"
51
+ fi
52
+ echo "$HASH" > "/tmp/architect-reviewed-${SESSION_ID}.hash"
53
+ fi
54
+
55
+ ;;
56
+ esac
57
+
58
+ exit 0
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ # Architecture - PreToolUse enforcement hook for ExitPlanMode
3
+ # BLOCKS ExitPlanMode until architect has reviewed the plan against ADRs.
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ source "$SCRIPT_DIR/lib/architect-gate.sh"
7
+
8
+ INPUT=$(cat)
9
+
10
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
11
+
12
+ if [ -z "$SESSION_ID" ]; then
13
+ architect_gate_parse_error
14
+ exit 0
15
+ fi
16
+
17
+ # Only gate if the project has architecture decisions
18
+ if [ ! -d "docs/decisions" ]; then
19
+ exit 0
20
+ fi
21
+
22
+ # Check gate
23
+ if check_architect_gate "$SESSION_ID"; then
24
+ exit 0
25
+ fi
26
+
27
+ cat <<EOF
28
+ {
29
+ "hookSpecificOutput": {
30
+ "hookEventName": "PreToolUse",
31
+ "permissionDecision": "deny",
32
+ "permissionDecisionReason": "BLOCKED: Architect must review the plan file before exiting plan mode. You MUST first delegate to wr-architect:agent using the Agent tool (subagent_type: 'wr-architect:agent') to review the plan against existing decisions in docs/decisions/. After the review completes, this will be unblocked automatically."
33
+ }
34
+ }
35
+ EOF
36
+ exit 0
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+ # Architecture - PostToolUse hook for Edit/Write
3
+ # Refreshes the stored decision hash after an allowed write to docs/decisions/.
4
+ # This prevents drift detection from invalidating the marker when creating
5
+ # new decision files that the architect just approved.
6
+
7
+ # Portable hash: tries md5sum, falls back to md5 -r, then shasum
8
+ _hashcmd() { md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null; }
9
+
10
+ INPUT=$(cat)
11
+
12
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') || true
13
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
14
+
15
+ if [ -z "$SESSION_ID" ] || [ -z "$FILE_PATH" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ # Only act on writes to docs/decisions/
20
+ case "$FILE_PATH" in
21
+ */docs/decisions/*|docs/decisions/*)
22
+ ;;
23
+ *)
24
+ exit 0
25
+ ;;
26
+ esac
27
+
28
+ MARKER="/tmp/architect-reviewed-${SESSION_ID}"
29
+ HASH_FILE="/tmp/architect-reviewed-${SESSION_ID}.hash"
30
+
31
+ # Only refresh if a valid marker exists
32
+ 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)
35
+ else
36
+ HASH="none"
37
+ fi
38
+ echo "$HASH" > "$HASH_FILE"
39
+ fi
40
+
41
+ exit 0
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # Architecture - Stop hook
3
+ # Removes the architect session marker so the next turn requires a fresh review.
4
+ # Mirrors: voice-tone-reset-marker.sh
5
+
6
+ INPUT=$(cat)
7
+
8
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
9
+
10
+ if [ -n "$SESSION_ID" ]; then
11
+ rm -f "/tmp/architect-reviewed-${SESSION_ID}"
12
+ rm -f "/tmp/architect-reviewed-${SESSION_ID}.hash"
13
+ fi
14
+
15
+ exit 0
@@ -0,0 +1,18 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-detect.sh" }] }
5
+ ],
6
+ "PreToolUse": [
7
+ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-enforce-edit.sh" }] },
8
+ { "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-plan-enforce.sh" }] }
9
+ ],
10
+ "PostToolUse": [
11
+ { "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-mark-reviewed.sh" }] },
12
+ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-refresh-hash.sh" }] }
13
+ ],
14
+ "Stop": [
15
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-reset-marker.sh" }] }
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,58 @@
1
+ #!/bin/bash
2
+ # Shared gate logic for architect enforcement hooks.
3
+ # Sourced by architect-enforce-edit.sh and architect-plan-enforce.sh.
4
+ # Provides: check_architect_gate
5
+
6
+ # Source shared portable helpers (_mtime, _hashcmd)
7
+ _ARCHITECT_GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$_ARCHITECT_GATE_DIR/gate-helpers.sh"
9
+
10
+ # Check architect gate marker. Returns 0 if marker is valid (allow), 1 if invalid (deny).
11
+ # Usage: check_architect_gate "$SESSION_ID"
12
+ check_architect_gate() {
13
+ local SESSION_ID="$1"
14
+ local MARKER="/tmp/architect-reviewed-${SESSION_ID}"
15
+ local TTL_SECONDS="${ARCHITECT_TTL:-600}"
16
+
17
+ if [ -n "$SESSION_ID" ] && [ -f "$MARKER" ]; then
18
+ local NOW=$(date +%s)
19
+ local MARKER_TIME=$(_mtime "$MARKER")
20
+ local AGE=$(( NOW - MARKER_TIME ))
21
+ if [ "$AGE" -lt "$TTL_SECONDS" ]; then
22
+ # TTL still valid -- check for decision drift
23
+ local HASH_FILE="/tmp/architect-reviewed-${SESSION_ID}.hash"
24
+ if [ -f "$HASH_FILE" ]; then
25
+ local STORED=$(cat "$HASH_FILE")
26
+ 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)
29
+ else
30
+ CURRENT="none"
31
+ fi
32
+ if [ "$STORED" != "$CURRENT" ]; then
33
+ rm -f "$MARKER" "$HASH_FILE"
34
+ return 1 # Drift detected, deny
35
+ else
36
+ touch "$MARKER" # Slide TTL window forward
37
+ return 0 # Valid, allow
38
+ fi
39
+ else
40
+ touch "$MARKER" # Slide TTL window forward
41
+ return 0 # No hash = old marker format, allow
42
+ fi
43
+ else
44
+ rm -f "$MARKER"
45
+ return 1 # TTL expired, deny
46
+ fi
47
+ fi
48
+
49
+ return 1 # No marker, deny
50
+ }
51
+
52
+ # Emit fail-closed deny JSON for parse failures
53
+ architect_gate_parse_error() {
54
+ cat <<'EOF'
55
+ { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny",
56
+ "permissionDecisionReason": "BLOCKED: Could not parse hook input. Gate is fail-closed." } }
57
+ EOF
58
+ }
@@ -0,0 +1,174 @@
1
+ #!/bin/bash
2
+ # Shared portable helpers for gate enforcement hooks.
3
+ # Sourced by architect-gate.sh, risk-gate.sh, and all hook scripts.
4
+ # Provides: _mtime, _hashcmd, _doc_exclusions, _err_trap, _get_*
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # Portable utilities
8
+ # ---------------------------------------------------------------------------
9
+
10
+ # Portable mtime: tries GNU stat, falls back to macOS stat
11
+ _mtime() { stat -c%Y "$1" 2>/dev/null || /usr/bin/stat -f%m "$1" 2>/dev/null || echo 0; }
12
+
13
+ # Portable hash: tries md5sum, falls back to md5 -r, then shasum
14
+ _hashcmd() { md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null; }
15
+
16
+ # Paths excluded from pipeline state hashing and docs-only detection.
17
+ _doc_exclusions() {
18
+ echo ':!docs/' ':!.risk-reports/' ':!.changeset/' ':!governance/' ':!.claude/plans/' ':!CLAUDE.md' ':!AGENTS.md' ':!PRINCIPLES.md' ':!DECISION-MANAGEMENT.md' ':!AGENTIC_RISK_REGISTER.md' ':!PROBLEM-MANAGEMENT.md'
19
+ }
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # ERR trap: outputs diagnostic JSON on hook errors (P010)
23
+ # Usage: source gate-helpers.sh at top of hook, then call _enable_err_trap
24
+ # ---------------------------------------------------------------------------
25
+
26
+ _enable_err_trap() {
27
+ trap '_err_trap_handler "$BASH_SOURCE" "$LINENO" "$BASH_COMMAND"' ERR
28
+ }
29
+
30
+ _err_trap_handler() {
31
+ local script="$1" line="$2" cmd="$3"
32
+ local name
33
+ name=$(basename "$script" 2>/dev/null || echo "$script")
34
+ # Output diagnostic as systemMessage so it's visible in conversation
35
+ cat <<EOF
36
+ {
37
+ "systemMessage": "Hook error in ${name} at line ${line}: ${cmd}"
38
+ }
39
+ EOF
40
+ }
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # JSON input parsing: standardised helpers replacing inline python3
44
+ # Each reads from _HOOK_INPUT (set by the hook before calling these)
45
+ # ---------------------------------------------------------------------------
46
+
47
+ # Store hook input for reuse by parsing helpers
48
+ _HOOK_INPUT=""
49
+
50
+ _parse_input() {
51
+ _HOOK_INPUT=$(cat)
52
+ }
53
+
54
+ _get_tool_name() {
55
+ echo "$_HOOK_INPUT" | python3 -c "
56
+ import sys, json
57
+ try:
58
+ data = json.load(sys.stdin)
59
+ print(data.get('tool_name', ''))
60
+ except:
61
+ print('')
62
+ " 2>/dev/null || echo ""
63
+ }
64
+
65
+ _get_session_id() {
66
+ echo "$_HOOK_INPUT" | python3 -c "
67
+ import sys, json
68
+ try:
69
+ data = json.load(sys.stdin)
70
+ print(data.get('session_id', ''))
71
+ except:
72
+ print('')
73
+ " 2>/dev/null || echo ""
74
+ }
75
+
76
+ _get_command() {
77
+ echo "$_HOOK_INPUT" | python3 -c "
78
+ import sys, json
79
+ try:
80
+ data = json.load(sys.stdin)
81
+ print(data.get('tool_input', {}).get('command', ''))
82
+ except:
83
+ print('')
84
+ " 2>/dev/null || echo ""
85
+ }
86
+
87
+ _get_file_path() {
88
+ echo "$_HOOK_INPUT" | python3 -c "
89
+ import sys, json
90
+ try:
91
+ data = json.load(sys.stdin)
92
+ ti = data.get('tool_input', {})
93
+ print(ti.get('file_path', ti.get('path', '')))
94
+ except:
95
+ print('')
96
+ " 2>/dev/null || echo ""
97
+ }
98
+
99
+ _get_subagent_type() {
100
+ echo "$_HOOK_INPUT" | python3 -c "
101
+ import sys, json
102
+ try:
103
+ data = json.load(sys.stdin)
104
+ print(data.get('tool_input', {}).get('subagent_type', ''))
105
+ except:
106
+ print('')
107
+ " 2>/dev/null || echo ""
108
+ }
109
+
110
+ _get_user_prompt() {
111
+ echo "$_HOOK_INPUT" | python3 -c "
112
+ import sys, json
113
+ try:
114
+ data = json.load(sys.stdin)
115
+ print(data.get('user_prompt', ''))
116
+ except:
117
+ print('')
118
+ " 2>/dev/null || echo ""
119
+ }
120
+
121
+ _get_tool_output() {
122
+ echo "$_HOOK_INPUT" | python3 -c "
123
+ import sys, json
124
+ try:
125
+ data = json.load(sys.stdin)
126
+ # PostToolUse provides tool_response (dict with content array), not tool_output
127
+ tr = data.get('tool_response', {})
128
+ if isinstance(tr, dict):
129
+ content = tr.get('content', [])
130
+ if isinstance(content, list):
131
+ texts = [c.get('text', '') for c in content if isinstance(c, dict) and c.get('type') == 'text']
132
+ if texts:
133
+ print('\n'.join(texts))
134
+ sys.exit(0)
135
+ # Fallback for older/different hook formats
136
+ print(data.get('tool_output', ''))
137
+ except:
138
+ print('')
139
+ " 2>/dev/null || echo ""
140
+ }
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Session-scoped tmp directory for risk files
144
+ # ---------------------------------------------------------------------------
145
+
146
+ # Returns the session-scoped directory for risk temp files.
147
+ # Creates the directory if it doesn't exist.
148
+ # Usage: DIR=$(_risk_dir "$SESSION_ID"); echo "1" > "$DIR/commit"
149
+ _risk_dir() {
150
+ local sid="$1"
151
+ local dir="${TMPDIR:-/tmp}/claude-risk-${sid}"
152
+ mkdir -p "$dir"
153
+ echo "$dir"
154
+ }
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Non-doc file detection for WIP gating
158
+ # ---------------------------------------------------------------------------
159
+
160
+ _is_doc_file() {
161
+ local file_path="$1"
162
+ local EXCL
163
+ EXCL=$(_doc_exclusions)
164
+ for pattern in $EXCL; do
165
+ local clean="${pattern#:!}"
166
+ case "$file_path" in
167
+ *"$clean"*) return 0 ;;
168
+ esac
169
+ done
170
+ case "$file_path" in
171
+ *.claude/*|*.risk-reports/*|*RISK-POLICY.md) return 0 ;;
172
+ esac
173
+ return 1
174
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@windyroad/architect",
3
+ "version": "0.2.0",
4
+ "description": "Architecture decision enforcement for AI coding agents",
5
+ "bin": {
6
+ "windyroad-architect": "./bin/install.mjs"
7
+ },
8
+ "type": "module",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/windyroad/agent-plugins.git",
13
+ "directory": "packages/architect"
14
+ },
15
+ "keywords": [
16
+ "claude-code",
17
+ "claude-code-plugin",
18
+ "ai-agent",
19
+ "ai-coding"
20
+ ],
21
+ "files": [
22
+ "bin/",
23
+ "agents/",
24
+ "hooks/",
25
+ "skills/",
26
+ ".claude-plugin/"
27
+ ]
28
+ }
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: wr:adr
3
+ description: Create a new Architecture Decision Record (MADR 4.0) in docs/decisions/. Examines existing decisions, asks about the problem and options, and writes a properly formatted ADR.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ # Architecture Decision Record Generator
8
+
9
+ Create a new ADR in `docs/decisions/` following MADR 4.0 format. The wr-architect:agent reviews these files to enforce architectural compliance.
10
+
11
+ ## Steps
12
+
13
+ ### 1. Discover existing decisions
14
+
15
+ Scan for existing ADRs:
16
+ - Glob `docs/decisions/*.md` (skip `README.md`)
17
+ - Note the highest numbered decision to determine the next sequence number
18
+ - Read any decisions related to the topic being discussed (if the user has mentioned a topic)
19
+ - If `docs/decisions/` does not exist, create it
20
+
21
+ ### 2. Gather context from the user
22
+
23
+ You MUST use the AskUserQuestion tool to collect the decision context. Do not proceed to step 3 until you have answers.
24
+
25
+ Ask the user:
26
+
27
+ 1. **What is the decision about?** A brief title and the problem being solved.
28
+ 2. **What options were considered?** At least 2 alternatives (including "do nothing" if applicable). For each option, ask for key pros and cons.
29
+ 3. **What was chosen and why?** The selected option and the primary reason.
30
+ 4. **Who are the decision-makers?** Who made or is making this decision.
31
+ 5. **Any consequences to note?** Known good, neutral, or bad outcomes.
32
+
33
+ If the user has already provided this context in the conversation (e.g., as arguments), use what they've given and only ask about what's missing.
34
+
35
+ ### 3. Determine sequence number and filename
36
+
37
+ - Next number = highest existing decision number + 1 (or 001 if none exist)
38
+ - Filename: `NNN-decision-title-in-kebab-case.proposed.md`
39
+ - Pad the number to 3 digits (001, 002, ... 010, 011, etc.)
40
+
41
+ ### 4. Write the ADR
42
+
43
+ Write the file to `docs/decisions/` with this structure:
44
+
45
+ ```markdown
46
+ ---
47
+ status: "proposed"
48
+ date: YYYY-MM-DD
49
+ decision-makers: [from user input]
50
+ consulted: [from user input, or empty list]
51
+ informed: [from user input, or empty list]
52
+ reassessment-date: YYYY-MM-DD # 3 months from today
53
+ ---
54
+
55
+ # Title
56
+
57
+ ## Context and Problem Statement
58
+
59
+ [What problem does this solve? Why is a decision needed now?]
60
+
61
+ ## Decision Drivers
62
+
63
+ - [Key factors influencing the decision]
64
+
65
+ ## Considered Options
66
+
67
+ 1. **Option A** - Brief description
68
+ 2. **Option B** - Brief description
69
+
70
+ ## Decision Outcome
71
+
72
+ Chosen option: **"Option X"**, because [primary justification].
73
+
74
+ ## Consequences
75
+
76
+ ### Good
77
+
78
+ - [Positive outcomes]
79
+
80
+ ### Neutral
81
+
82
+ - [Trade-offs that are neither clearly good nor bad]
83
+
84
+ ### Bad
85
+
86
+ - [Negative outcomes or risks accepted]
87
+
88
+ ## Confirmation
89
+
90
+ [How to verify implementation compliance. Concrete, testable criteria.]
91
+
92
+ ## Pros and Cons of the Options
93
+
94
+ ### Option A
95
+
96
+ - Good: [advantage]
97
+ - Bad: [disadvantage]
98
+
99
+ ### Option B
100
+
101
+ - Good: [advantage]
102
+ - Bad: [disadvantage]
103
+
104
+ ## Reassessment Criteria
105
+
106
+ [When should this decision be revisited? What conditions would trigger a review?]
107
+ ```
108
+
109
+ Use today's date for the `date` field. Set `reassessment-date` to 3 months from today unless the user specifies otherwise.
110
+
111
+ ### 5. Confirm with the user
112
+
113
+ Present the written ADR and use AskUserQuestion to ask:
114
+ 1. Does the problem statement accurately capture the situation?
115
+ 2. Are the pros/cons fair and complete?
116
+ 3. Are the confirmation criteria testable?
117
+ 4. Should anyone else be listed as consulted or informed?
118
+
119
+ Apply any feedback by editing the file.
120
+
121
+ ### 6. Handle supersession (if applicable)
122
+
123
+ If the user mentions this decision replaces an existing one:
124
+ 1. Add `supersedes: [NNN-old-decision-title]` to the new decision's frontmatter
125
+ 2. Rename the old decision file from `.accepted.md` (or `.proposed.md`) to `.superseded.md`
126
+ 3. Update the old decision's frontmatter status to `superseded`
127
+ 4. Add a "Superseded by" section to the old decision referencing the new one
128
+
129
+ $ARGUMENTS