@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.
- package/.claude-plugin/plugin.json +5 -0
- package/agents/agent.md +219 -0
- package/bin/install.mjs +42 -0
- package/hooks/architect-detect.sh +47 -0
- package/hooks/architect-enforce-edit.sh +72 -0
- package/hooks/architect-mark-reviewed.sh +58 -0
- package/hooks/architect-plan-enforce.sh +36 -0
- package/hooks/architect-refresh-hash.sh +41 -0
- package/hooks/architect-reset-marker.sh +15 -0
- package/hooks/hooks.json +18 -0
- package/hooks/lib/architect-gate.sh +58 -0
- package/hooks/lib/gate-helpers.sh +174 -0
- package/package.json +28 -0
- package/skills/wr:adr/SKILL.md +129 -0
package/agents/agent.md
ADDED
|
@@ -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
|
package/bin/install.mjs
ADDED
|
@@ -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
|
package/hooks/hooks.json
ADDED
|
@@ -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
|