references/ste-rules.md
<!-- SYNC NOTE: Mirror of skills/authoring/references/ste-rules.md for flat-install resolution (only sibling skill directories ship). Do not edit directly; update the canonical file and re-copy. Collapses when adlc-workspace#34 lands. -->
# Simplified Technical English (STE) & Complex-Decision Map Contract
This shared reference defines the writing rules and decision-map schemas for clarification points across all clarification skills (`product-clarify`, `architect-clarify`, `change-clarify`, `evals-clarify`).
---
## 1. Simplified Technical English (STE) Rules
Every clarification point must follow these core writing principles distilled from ASD-STE100:
1. **Short Sentences**: Keep sentences $\le 20$ words.
2. **One Idea Per Sentence**: Express one thought, condition, or requirement per sentence.
3. **Active Voice**: The actor must precede the action. Avoid passive constructions.
4. **Imperative for the Ask**: When asking the human a question or requesting action, use direct imperative commands (e.g., "Choose an option", "Define the missing boundary").
5. **Named Referents**: Never use bare pronouns (`it`, `they`, `this`, `that`). Always repeat the specific noun (e.g., "the PDR", "the worker process", "the timeout").
6. **Plain Words**: Choose simple, direct, unambiguous words. Avoid idioms, jargon, and vague qualifiers. No licensed dictionary is enforced; focus on clarity and brevity.
### STE-Lite Subset (for Context Injections and Boots)
For session boots and context injections where tokens are constrained:
- Sentence length $\le 15$ words.
- Tables over paragraphs.
- Zero filler words.
- Subject-verb-object structure strictly enforced.
---
## 2. Deterministic Clarification Tier Rule
Clarification points are classified strictly by consequence scope, **not** by severity:
- **Complex Tier ($\ge 2$ Boundaries Crossed)**:
Draw the full ASCII decision map.
- **Simple Tier (0 or 1 Boundary Crossed)**:
Use plain STE prose only, regardless of whether the gap is marked CRITICAL or HIGH. Severity determines presentation order; severity **never** upgrades a single-boundary item to a decision map.
### The Four Boundaries
| Boundary | Definition | Example |
|----------|------------|---------|
| `record` | Affects other decision records, schemas, or memory indexes beyond the active file. | Modifies ADR frontmatter schema or PDR dependency chain. |
| `lane` | Crosses between functional execution lanes (e.g., product $\leftrightarrow$ architecture, verify $\leftrightarrow$ deploy). | Product requirement forces architectural pattern change. |
| `team` | Impacts team AI directives, shared constitution, or cross-repo developer workflows. | Amends team constitution or alters git commit conventions. |
| `now` | Introduces an immediate migration, breaking change, flag day, or irrecoverable state. | Drops command shims without aliases or removes active DB table. |
---
## 3. Decision Map Schema & Anatomy (for $\ge 2$ Boundaries)
When a clarification point crosses two or more boundaries, render the structured ASCII decision map below.
### Anatomy Checklist
1. **WHAT / WHY**: One-line STE problem statement and business/technical driver.
2. **Boundaries Crossed**: Explicit list of crossed boundaries (`record`, `lane`, `team`, `now`).
3. **Reach Fan-Out**: Visual ASCII box showing which boundary dimensions are touched.
4. **Options Matrix**: Candidates $\times$ discriminating axes.
5. **IF-UNDECIDED**: Explicit consequence if the human skips or defers the decision.
6. **Undrawn Slots (`?`)**: Any missing context, unstated trade-off, or unknown consequence must render as an undrawn slot (`?`), **never** as a prose complaint or conversational apology.
### Map Anatomy Template
```text
+-------------------------------------------------------------------------+
| DECISION MAP: [ID] - [Title] |
| WHAT / WHY: [One STE sentence stating the issue and the reason] |
| BOUNDARIES CROSSED: [List 2 or more: record | lane | team | now] |
+-------------------------------------------------------------------------+
| REACH FAN-OUT: |
| +-[record] --> [Touched record/index or '?'] |
| +-[lane] --> [Touched lane or '?'] |
| +-[team] --> [Touched team convention or '?'] |
| +-[now] --> [Immediate breaking consequence or '?'] |
+-------------------------------------------------------------------------+
| CANDIDATES & TRADEOFFS: |
| |
| Option A: [Title] |
| + Pro: [STE pro] |
| - Con: [STE con] |
| * Cross-boundary impact: [Impact or '?'] |
| |
| Option B: [Title] |
| + Pro: [STE pro] |
| - Con: [STE con] |
| * Cross-boundary impact: [Impact or '?'] |
| |
| [?] Missing Alternative: [Undrawn candidate slot if trade-offs incomplete]|
+-------------------------------------------------------------------------+
| IF-UNDECIDED: [Consequence if decision deferred; defaults to halt/safe] |
+-------------------------------------------------------------------------+
```
---
## 4. Plain STE Format (for 0 or 1 Boundary)
When a gap touches zero or one boundary (e.g., local file formatting, missing success metric in a single PDR, local phrasing):
```markdown
## Clarification [N]: [ID] - [Gap Type]
**Context**: [One STE sentence quoting or identifying the item]
**Gap**: [One STE sentence describing the missing element]
**Question**: [One direct imperative STE question]
**Suggested Options**:
- **A**: [Option text]
- **B**: [Option text]
- **C**: [Custom response]
```
Do not draw an ASCII map for 0 or 1 boundary, even if the gap is CRITICAL.
scripts/bash/ascii-generator.sh
#!/usr/bin/env bash
# ASCII diagram generator for architecture views
# Generates ASCII art for all 7 Rozanski & Woods architectural views
#
# Core Views (always generated with --views=core):
# - Context, Functional, Information, Development, Deployment
#
# Optional Views (only with --views=all or --views=concurrency,operational):
# - Concurrency (3.4)
# - Operational (3.7)
# Generate Context View diagram (system boundary)
# CRITICAL: System must be shown as a SINGLE BLACKBOX - no internal components
# Only show: Stakeholders (human actors) + External systems (third-party, outside your control)
# DO NOT show: Internal databases, services, caches (those go in Deployment/Functional views)
generate_context_ascii() {
local system_name="${1:-System}"
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Context View Diagram │
│ (System shown as single blackbox) │
└─────────────────────────────────────────────────────────────┘
STAKEHOLDERS EXTERNAL SYSTEMS
──────────── ────────────────
┌──────────────┐ ┌────────────────┐
│ Users │ │ Payment │
│ (End Users) │ │ Provider │
└──────┬───────┘ │ (External) │
│ └───────┬────────┘
│ Uses │
│ │ Processes
▼ │ payments
┌─────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────┐ │
│ │ │ │
│ │ SYSTEM │◄─────────────────┘
│ │ (This App) │
│ │ │──────────────────┐
│ └─────────────────┘ │
│ ▲ │
│ │ │
└─────────────────────────│───────────────────────────────┘
│ Manages │
┌──────────────┐ │ ▼
│ Admins │──────────┘ ┌────────────────┐
│(Administrators) │ Identity │
└──────────────┘ │ Provider │
│ (External) │
└────────────────┘
Legend:
─────► Data/control flow crossing system boundary
[SYSTEM] = Single blackbox (internal details in Functional/Deployment views)
(External) = Third-party services outside your control
EOF
}
# Generate Functional View diagram (component interactions)
generate_functional_ascii() {
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Functional View Diagram │
└─────────────────────────────────────────────────────────────┘
User Request
│
▼
┌──────────────────────┐
│ API Gateway │
└──────────┬───────────┘
│
┏──────┴──────┓
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Authentication│ │ Business │
│ Service │ │ Logic │
└───────┬───────┘ └───────┬───────┘
│ │
└──────────┬───────┘
▼
┌──────────────────────┐
│ Data Access Layer │
└──────────┬───────────┘
│
┏──────┴──────┓
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Database │ │ Cache │
└─────────────┘ └─────────────┘
EOF
}
# Generate Information View diagram (data entities)
generate_information_ascii() {
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Information View Diagram │
└─────────────────────────────────────────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │1 n │ Order │1 n │OrderItem │
├──────────┤◄────────├──────────┤◄────────├──────────┤
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ email │ │ user_id │ │ order_id │
│ name │ │ status │ │product_id│
│created_at│ │ total │ │ quantity │
└──────────┘ │created_at│ │ price │
└──────────┘ └────┬─────┘
│
│n
▼
┌──────────┐
│ Product │
├──────────┤
│ id (PK) │
│ name │
│ price │
│ sku │
└──────────┘
Key: PK = Primary Key, FK = Foreign Key
1 = One, n = Many, ◄──── = Relationship
EOF
}
# Generate Concurrency View diagram (process timeline)
# OPTIONAL VIEW: Only generated when --views=all or --views=concurrency
generate_concurrency_ascii() {
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Concurrency View Diagram │
└─────────────────────────────────────────────────────────────┘
User WebServer AppServer Worker Database
│ │ │ │ │
│─Request─>│ │ │ │
│ │ │ │ │
│ │─Process──>│ │ │
│ │ │ │ │
│ │ │──Query───────────────>│
│ │ │ │ │
│ │ │<──Results─────────────│
│ │ │ │ │
│ │ │─Queue Job─>│ │
│ │ │ │ │
│ │ │ │──Update──>│
│ │<─Response─│ │ │
│ │ │ │<─Done────│
│<─Result─│ │ │ │
│ │ │ │ │
Processes run concurrently:
- Main Request/Response Flow (vertical)
- Background Worker Processing (parallel)
EOF
}
# Generate Development View diagram (module dependencies)
generate_development_ascii() {
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Development View Diagram │
└─────────────────────────────────────────────────────────────┘
Directory Structure:
project-root/
├── src/
│ ├── api/ ◄─── API Layer (Controllers)
│ │ └── routes/ │
│ │ │ depends on
│ ├── services/ ◄───┘
│ │ └── business/ ◄─── Services Layer
│ │ │
│ ├── repositories/ │ depends on
│ │ └── data/ ◄───┘
│ │ ◄─── Data Access Layer
│ ├── models/ │
│ │ └── entities/ ◄───┘ depends on
│ │ ◄─── Models (Shared)
│ └── utils/
│ └── helpers/ ◄─── Utilities (Shared)
│
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
│
└── docs/
Dependency Rules:
- API → Services → Repositories → Models
- No circular dependencies
- Utilities shared across layers
EOF
}
# Generate Deployment View diagram (infrastructure)
generate_deployment_ascii() {
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Deployment View Diagram │
└─────────────────────────────────────────────────────────────┘
Internet
│
▼
┌────────────────┐
│ Load Balancer │
│ (ALB/Nginx) │
└────────┬───────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Web Server 1 │ │ Web Server 2 │ │ Web Server 3 │
│ (Public) │ │ (Public) │ │ (Public) │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌────────────────┐
│ App Tier │
│ (Private) │
└────────┬───────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Database │ │ Redis Cache │ │ Object │
│ (Primary) │ │ │ │ Storage │
│ (Private) │ │ (Private) │ │ (S3/Blob) │
└───────┬───────┘ └───────────────┘ └───────────────┘
│
▼
┌───────────────┐
│ Database │
│ (Replica) │
│ (Private) │
└───────────────┘
EOF
}
# Generate Operational View diagram (operational workflow)
# OPTIONAL VIEW: Only generated when --views=all or --views=operational
generate_operational_ascii() {
cat <<'EOF'
┌─────────────────────────────────────────────────────────────┐
│ Operational View Diagram │
└─────────────────────────────────────────────────────────────┘
Deployment Workflow:
START
│
▼
┌──────────────┐
│ Run Tests │──┐
│ & Build │ │ Fail
└──────┬───────┘ │
│ Pass │
▼ │
┌──────────────┐ │
│ Deploy to │ │
│ Staging │ │
└──────┬───────┘ │
│ │
▼ │
┌──────────────┐ │
│Run Staging │──┤ Fail
│ Tests │ │
└──────┬───────┘ │
│ Pass │
▼ │
┌──────────────┐ │
│ Manual │──┤ Reject
│ Approval? │ │
└──────┬───────┘ │
│ Approve │
▼ │
┌──────────────┐ │
│ Deploy to │ │
│ Production │ │
└──────┬───────┘ │
│ │
▼ │
┌──────────────┐ │
│ Monitor │ │
│ Health │ │
└──────┬───────┘ │
│ │
┌───┴───┐ │
│Healthy│ │
▼ ▼ │
YES NO │
│ │ │
│ ┌────────┐│
│ │Rollback││
│ └────┬───┘│
│ │ │
▼ ▼ ▼
END Alert Team
Monitoring: Continuous
Backups: Daily automated
On-call: 24/7 rotation
EOF
}
# Main function to generate diagram for a specific view
# Usage: generate_ascii_diagram "context" "System Name"
generate_ascii_diagram() {
local view_type="$1"
local system_name="${2:-System}"
case "$view_type" in
context)
generate_context_ascii "$system_name"
;;
functional)
generate_functional_ascii
;;
information)
generate_information_ascii
;;
concurrency)
generate_concurrency_ascii
;;
development)
generate_development_ascii
;;
deployment)
generate_deployment_ascii
;;
operational)
generate_operational_ascii
;;
*)
echo "Error: Unknown view type '$view_type'" >&2
return 1
;;
esac
}
# Export functions for use in other scripts
export -f generate_context_ascii
export -f generate_functional_ascii
export -f generate_information_ascii
export -f generate_concurrency_ascii
export -f generate_development_ascii
export -f generate_deployment_ascii
export -f generate_operational_ascii
export -f generate_ascii_diagram
scripts/bash/common.sh
#!/usr/bin/env bash
#
# Minimal common helpers for adlc-skills architect-* skills.
# Bundled with the skill so it works standalone, outside the Spec Kit extension system.
# Layout roots (.adlc/ machine pipeline; docs/adlc/ human-facing deliverables)
ADLC_DIR="${ADLC_DIR:-.adlc}"
DOCS_ADLC_DIR="${DOCS_ADLC_DIR:-docs/adlc}"
DOCS_ADLC_MEMORY="${DOCS_ADLC_MEMORY:-docs/adlc/memory}"
DOCS_ADLC_PRODUCT="${DOCS_ADLC_PRODUCT:-docs/adlc/product}"
DOCS_ADLC_ARCHITECT="${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}"
ADLC_DRAFTS="${ADLC_DRAFTS:-.adlc/drafts}"
# Locate the project root by walking up from CWD until we find .adlc or .git.
_get_project_root() {
local dir
dir="$(pwd)"
while [ "$dir" != "/" ]; do
if [ -d "$dir/.adlc" ] || [ -d "$dir/.git" ]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
# Fallback: current working directory.
echo "$(pwd)"
}
# Seed bundled templates into the project's .adlc/templates/ directory.
# Only copies files when the destination does not exist, so user customizations are preserved.
_seed_templates() {
local repo_root="${1:-$(_get_project_root)}"
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local src_dir="$script_dir/../../templates"
local dest_dir="$repo_root/${ADLC_DIR:-.adlc}/templates"
[ -d "$src_dir" ] || return 0
mkdir -p "$dest_dir"
local f basename
for f in "$src_dir"/*; do
[ -e "$f" ] || continue
basename=$(basename "$f")
if [ -d "$f" ]; then
if [ ! -d "$dest_dir/$basename" ]; then
cp -r "$f" "$dest_dir/$basename"
fi
else
if [ ! -f "$dest_dir/$basename" ]; then
cp "$f" "$dest_dir/$basename"
fi
fi
done
}
# Emulates the get_feature_paths function from the Spec Kit common.sh.
# The architect setup script evals this output to obtain REPO_ROOT.
get_feature_paths() {
local repo_root
repo_root="$(_get_project_root)"
_seed_templates "$repo_root"
echo "REPO_ROOT=\"$repo_root\""
}
# Get architecture diagram format (mermaid or ascii).
# Override via ARCHITECTURE_DIAGRAM_FORMAT env var; defaults to "mermaid".
# Invalid values fall back to "mermaid".
get_architecture_diagram_format() {
local format="${ARCHITECTURE_DIAGRAM_FORMAT:-mermaid}"
if [[ "$format" == "mermaid" || "$format" == "ascii" ]]; then
echo "$format"
else
echo "mermaid"
fi
}
# Validate Mermaid diagram syntax (lightweight regex validation).
# Returns 0 if valid, 1 if invalid.
# Args: $1 - Mermaid code string
validate_mermaid_syntax() {
local mermaid_code="$1"
# Check if empty
if [[ -z "$mermaid_code" ]]; then
return 1
fi
# Check for basic Mermaid diagram types
if ! echo "$mermaid_code" | grep -qE '^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|journey|gitGraph|mindmap|timeline)'; then
return 1
fi
# Check for balanced brackets/parentheses (simplified)
local open_brackets close_brackets open_parens close_parens
open_brackets=$(echo "$mermaid_code" | grep -o '\[' | wc -l)
close_brackets=$(echo "$mermaid_code" | grep -o '\]' | wc -l)
open_parens=$(echo "$mermaid_code" | grep -o '(' | wc -l)
close_parens=$(echo "$mermaid_code" | grep -o ')' | wc -l)
if [[ $open_brackets -ne $close_brackets ]] || [[ $open_parens -ne $close_parens ]]; then
return 1
fi
# Basic syntax passed
return 0
}
scripts/bash/mermaid-generator.sh
#!/usr/bin/env bash
# Mermaid diagram generator for architecture views
# Generates Mermaid code for all 7 Rozanski & Woods architectural views
#
# Core Views (always generated with --views=core):
# - Context, Functional, Information, Development, Deployment
#
# Optional Views (only with --views=all or --views=concurrency,operational):
# - Concurrency (3.4)
# - Operational (3.7)
# Generate Context View diagram (system boundary)
# CRITICAL: System must be shown as a SINGLE BLACKBOX - no internal components
# Only show: Stakeholders (human actors) + External systems (third-party, outside your control)
# DO NOT show: Internal databases, services, caches (those go in Deployment/Functional views)
generate_context_mermaid() {
local system_name="${1:-System}"
cat <<'EOF'
graph TD
%% Stakeholders (human actors interacting with the system)
Users["👥 Users/Clients"]
Admins["👤 Administrators"]
%% THE SYSTEM - Single blackbox (NO internal components)
System["🏢 System<br/>(This Application)"]
%% External Systems (third-party services outside your control)
ExtPayment["💳 Payment Provider<br/>(External)"]
ExtAuth["🔐 Identity Provider<br/>(External)"]
ExtAPI["🌐 Partner API<br/>(External)"]
%% Stakeholder interactions
Users -->|"Uses"| System
Admins -->|"Manages"| System
%% External system integrations
System -->|"Processes payments"| ExtPayment
System -->|"Authenticates via"| ExtAuth
System -->|"Exchanges data"| ExtAPI
%% Styling
classDef systemNode fill:#f47721,stroke:#333,stroke-width:3px,color:#fff
classDef stakeholderNode fill:#4a9eff,stroke:#333,stroke-width:1px,color:#fff
classDef externalNode fill:#e0e0e0,stroke:#333,stroke-width:1px
class System systemNode
class Users,Admins stakeholderNode
class ExtPayment,ExtAuth,ExtAPI externalNode
EOF
}
# Generate Functional View diagram (component interactions)
generate_functional_mermaid() {
cat <<'EOF'
graph TD
APIGateway["API Gateway"]
AuthService["Authentication<br/>Service"]
BusinessLogic["Business Logic<br/>Layer"]
DataAccess["Data Access<br/>Layer"]
Cache["Cache Layer"]
APIGateway -->|Routes| AuthService
APIGateway -->|Routes| BusinessLogic
AuthService -->|Validates| BusinessLogic
BusinessLogic -->|Queries| DataAccess
BusinessLogic -->|Caches| Cache
DataAccess -->|Reads/Writes| Cache
classDef serviceNode fill:#4a9eff,stroke:#333,stroke-width:2px,color:#fff
classDef dataNode fill:#66c2a5,stroke:#333,stroke-width:2px,color:#fff
class APIGateway,AuthService,BusinessLogic serviceNode
class DataAccess,Cache dataNode
EOF
}
# Generate Information View diagram (data entities and relationships)
generate_information_mermaid() {
cat <<'EOF'
erDiagram
User ||--o{ Session : has
User ||--o{ Order : places
Order ||--|{ OrderItem : contains
OrderItem }o--|| Product : references
Product ||--o{ Inventory : tracked_in
User ||--o{ Address : has
Order }o--|| Address : ships_to
User {
int id PK
string email
string name
timestamp created_at
}
Order {
int id PK
int user_id FK
string status
decimal total
timestamp created_at
}
Product {
int id PK
string name
decimal price
string sku
}
EOF
}
# Generate Concurrency View diagram (process timeline)
# OPTIONAL VIEW: Only generated when --views=all or --views=concurrency
generate_concurrency_mermaid() {
cat <<'EOF'
sequenceDiagram
participant User
participant WebServer
participant AppServer
participant Worker
participant Database
User->>WebServer: HTTP Request
WebServer->>AppServer: Process Request
AppServer->>Database: Query Data
Database-->>AppServer: Return Results
par Background Processing
AppServer->>Worker: Queue Background Job
Worker->>Database: Update Records
end
AppServer-->>WebServer: Response
WebServer-->>User: HTTP Response
EOF
}
# Generate Development View diagram (module dependencies)
generate_development_mermaid() {
cat <<'EOF'
graph LR
API["🔌 API Layer"]
Services["⚙️ Services Layer"]
Repositories["💾 Repositories"]
Models["📦 Models"]
Utils["🛠️ Utilities"]
API -->|depends on| Services
Services -->|depends on| Repositories
Repositories -->|depends on| Models
Services -->|uses| Utils
API -->|uses| Utils
classDef layerNode fill:#9b59b6,stroke:#333,stroke-width:2px,color:#fff
classDef supportNode fill:#95a5a6,stroke:#333,stroke-width:1px,color:#fff
class API,Services,Repositories layerNode
class Models,Utils supportNode
EOF
}
# Generate Deployment View diagram (infrastructure)
generate_deployment_mermaid() {
cat <<'EOF'
graph TB
subgraph "Production Environment"
LB["⚖️ Load Balancer"]
subgraph "Application Tier"
Web1["Web Server 1"]
Web2["Web Server 2"]
Web3["Web Server 3"]
end
subgraph "Data Tier"
DB_Primary["🗄️ Database<br/>Primary"]
DB_Replica["🗄️ Database<br/>Replica"]
Cache["💾 Redis Cache"]
end
subgraph "Storage Tier"
S3["☁️ Object Storage"]
end
end
Internet["🌐 Internet"] -->|HTTPS| LB
LB -->|Distributes| Web1
LB -->|Distributes| Web2
LB -->|Distributes| Web3
Web1 -->|Reads/Writes| DB_Primary
Web2 -->|Reads/Writes| DB_Primary
Web3 -->|Reads/Writes| DB_Primary
DB_Primary -->|Replicates to| DB_Replica
Web1 -->|Caches| Cache
Web2 -->|Caches| Cache
Web3 -->|Caches| Cache
Web1 -->|Stores files| S3
Web2 -->|Stores files| S3
Web3 -->|Stores files| S3
classDef infraNode fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
classDef dataNode fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
class LB,Web1,Web2,Web3 infraNode
class DB_Primary,DB_Replica,Cache,S3 dataNode
EOF
}
# Generate Operational View diagram (operational workflow)
# OPTIONAL VIEW: Only generated when --views=all or --views=operational
generate_operational_mermaid() {
cat <<'EOF'
flowchart TD
Start([🚀 Deployment Initiated])
BuildTests{Run Tests<br/>& Build}
BuildSuccess[✅ Build Success]
BuildFail[❌ Build Failed]
Deploy[📦 Deploy to Staging]
StagingTests{Staging Tests<br/>Pass?}
ManualApproval{Manual<br/>Approval?}
ProdDeploy[🎯 Deploy to Production]
Monitor[📊 Monitor Health]
HealthCheck{System<br/>Healthy?}
Rollback[⏪ Rollback]
Alert[🚨 Alert Team]
Complete([✅ Deployment Complete])
Start --> BuildTests
BuildTests -->|Pass| BuildSuccess
BuildTests -->|Fail| BuildFail
BuildSuccess --> Deploy
BuildFail --> Alert
Deploy --> StagingTests
StagingTests -->|Pass| ManualApproval
StagingTests -->|Fail| Alert
ManualApproval -->|Approved| ProdDeploy
ManualApproval -->|Rejected| Alert
ProdDeploy --> Monitor
Monitor --> HealthCheck
HealthCheck -->|Healthy| Complete
HealthCheck -->|Issues| Rollback
Rollback --> Alert
classDef successNode fill:#27ae60,stroke:#333,stroke-width:2px,color:#fff
classDef errorNode fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
classDef processNode fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
class BuildSuccess,Complete successNode
class BuildFail,Alert,Rollback errorNode
class Deploy,ProdDeploy,Monitor processNode
EOF
}
# Main function to generate diagram for a specific view
# Usage: generate_mermaid_diagram "context" "System Name"
generate_mermaid_diagram() {
local view_type="$1"
local system_name="${2:-System}"
case "$view_type" in
context)
generate_context_mermaid "$system_name"
;;
functional)
generate_functional_mermaid
;;
information)
generate_information_mermaid
;;
concurrency)
generate_concurrency_mermaid
;;
development)
generate_development_mermaid
;;
deployment)
generate_deployment_mermaid
;;
operational)
generate_operational_mermaid
;;
*)
echo "Error: Unknown view type '$view_type'" >&2
return 1
;;
esac
}
# Export functions for use in other scripts
export -f generate_context_mermaid
export -f generate_functional_mermaid
export -f generate_information_mermaid
export -f generate_concurrency_mermaid
export -f generate_development_mermaid
export -f generate_deployment_mermaid
export -f generate_operational_mermaid
export -f generate_mermaid_diagram
scripts/bash/mermaid-lint.sh
#!/bin/bash
# Mermaid syntax validator (the mermaid detection pass) — flag + suggest, never write.
#
# Scans a markdown artifact for ```mermaid fenced blocks and reports one
# finding per broken block: location (file + line + fence index), severity,
# the rule violated, the offending line, and a minimal suggested patch.
#
# Read-only by contract (the lifecycle write scope lifecycle write scope): this script never
# writes, moves or reformats an artifact. Remediation stays with
# implement/human.
#
# Severity (the mermaid detection pass):
# HIGH = syntax-break — the block cannot render
# MEDIUM = style — renderable but non-mandated form, a body that
# carries no statement at all, or a type this rule
# set cannot vouch for (never a false HIGH)
#
# Comments and directives:
# `%% …` is a mermaid comment and `%%{ … }%%` is a mermaid directive. Both
# are legal anywhere inside a block and carry no diagram content — mermaid
# strips them before it parses the block. This validator therefore skips
# them wherever it reads a block: they are never the block's type
# declaration and never one of its statements.
#
# Usage:
# mermaid-lint.sh [--tsv] <file> [file...]
#
# Output:
# default — one readable block per finding (severity, rule, location,
# offending line, suggested patch)
# --tsv — one tab-separated line per finding, for machine consumption:
# severity \t rule \t file \t line \t fence \t message \t
# offending \t suggestion
# (no JSON: this script builds no JSON by hand, and pulls in no
# runtime dependency — runtime independence)
#
# Exit codes:
# 0 = no findings
# 1 = at least one HIGH (syntax-break) finding
# 2 = only MEDIUM (style) findings
# 3 = usage error (missing file)
#
# Rule set is documented once in AD v13.14.0 (the mermaid detection pass) and referenced by
# product-analyze and architect-analyze — not forked per skill.
set -u
TSV=false
FILES=()
for arg in "$@"; do
case "$arg" in
--tsv) TSV=true ;;
--help|-h)
sed -n '2,32p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
-*)
echo "mermaid-lint: unknown option '$arg'" >&2
exit 3
;;
*) FILES+=("$arg") ;;
esac
done
if [ "${#FILES[@]}" -eq 0 ]; then
echo "Usage: mermaid-lint.sh [--json] <file> [file...]" >&2
exit 3
fi
# ── rule tables ──────────────────────────────────────────────────────────────
# Known mermaid diagram keywords. A block whose first content line is not in
# this set is MEDIUM (unknown-but-plausible -> review prompt), never HIGH.
# `flow` and `chart` are NOT keywords: mermaid spells it `flowchart` (one
# word) or the legacy `graph`. A block opening with `chart TD` (or
# `flow chart TD`) therefore falls through to the MEDIUM M002 branch.
KNOWN_TYPES=" flowchart graph gantt journey erDiagram sequenceDiagram classDiagram stateDiagram stateDiagram-v2 pie mindmap timeline quadrantChart gitGraph requirementDiagram C4Context C4Container C4Component C4Dynamic C4Deployment zenuml block-beta sankey-beta packet-beta kanban architecture-beta xychart-beta treemap-beta radar-beta "
# The product mandate (product-implement) requires `flowchart`, not `graph`.
# `graph` still renders, so it is a style finding, not a syntax break.
MANDATED_FLOWCHART_TYPES=" flowchart graph "
# gantt statements that are structurally meaningful.
GANTT_KEYS=" dateFormat axisFormat excludes includes section todayMarker tickInterval weekday weekend title "
# A gantt task line: "<name> : <id>, <start>[, <end|duration>]". The
# `dateFormat` key is optional in mermaid, so a task line is a valid first
# statement — recognised separately from the keys above.
GANTT_TASK_RE='^[^:]+:[^,]+,[[:space:]]*[^,]+'
# A task-line-shaped statement that is not a complete task line ("<name> :"
# with no id/start): ambiguous, so it is reported MEDIUM, never a false HIGH.
GANTT_TASK_LOOSE_RE='^[^:]+:'
# sequenceDiagram statement heads.
#
# The keyword table must track mermaid's own grammar, not an impression of it:
# every head here was checked against `mermaid.parse()` (12.2.0) as a first
# statement. `title`, `autonumber`, `box`, `critical` and `break` are
# documented statements mermaid accepts, so leaving them out turned valid
# blocks into HIGH findings. `done` is deliberately absent — mermaid rejects
# it as a first statement.
SEQ_KEYS=" participant actor boundary control entity database collections queue alt else opt loop par and rect activate deactivate note link links over right left of title autonumber box critical break end "
HIGH=0
MEDIUM=0
FINDINGS=0
# Collapse tabs and newlines so a field can never break the line format.
flatten() {
printf '%s' "$1" | tr '\t\n\r' ' ' | sed -e 's/[[:space:]]\{2,\}/ /g' -e 's/^ //' -e 's/ $//'
}
# emit <severity> <rule> <file> <line> <fence_index> <message> <offending> <patch>
emit() {
local sev="$1" rule="$2" file="$3" line="$4" idx="$5" msg="$6" offending="$7" patch="$8"
FINDINGS=$((FINDINGS + 1))
if [ "$sev" = "HIGH" ]; then HIGH=$((HIGH + 1)); else MEDIUM=$((MEDIUM + 1)); fi
if [ "$TSV" = true ]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$sev" "$rule" "$(flatten "$file")" "$line" "$idx" \
"$(flatten "$msg")" "$(flatten "$offending")" "$(flatten "$patch")"
else
printf '%s %s %s:%s (fence #%s)\n' "$sev" "$rule" "$file" "$line" "$idx"
printf ' %s\n' "$msg"
printf ' offending: %s\n' "$offending"
printf ' suggestion: %s\n' "$patch"
fi
}
# A known keyword check: " word " membership in a space-delimited table.
has_word() {
case "$1" in
*" $2 "*) return 0 ;;
*) return 1 ;;
esac
}
# The first whitespace-delimited token of a line, leading whitespace ignored.
first_word() {
printf '%s' "$1" | sed -e 's/^[[:space:]]*//' | awk '{print $1}'
}
# Is this line a mermaid comment (`%% …`) or directive (`%%{ … }%%`)?
# The check is on the line's first non-space content: only a line whose own
# first non-space characters are `%%` is a comment. A `%%` *inside* a
# statement (`A --> B %% note`) is not a comment — mermaid rejects that form
# (it is a parse error, not a trailing comment). This rule set does not
# model it: such a line is still read as a statement, and no rule is raised
# for it here.
is_comment() {
case "$(printf '%s' "$1" | sed -e 's/^[[:space:]]*//')" in
'%%'*) return 0 ;;
*) return 1 ;;
esac
}
lint_file() {
local file="$1"
if [ ! -f "$file" ]; then
echo "mermaid-lint: file not found: $file" >&2
return 1
fi
local lineno=0 fence_index=0 in_block=0 block_start=0 block_header="" block_header_line=0
# content_lines counts only the lines that carry diagram content — the
# type declaration and its statements; comment_lines counts the stripped
# comments/directives. A block's verdict is driven by content_lines: a
# blank line and a comment carry no content, so neither of them changes
# the count.
local content_lines=0 comment_lines=0 body_first="" body_first_line=0 decl=""
local line
while IFS= read -r line || [ -n "$line" ]; do
lineno=$((lineno + 1))
line="${line%$'\r'}"
if [ "$in_block" -eq 0 ]; then
# Opening fence: a line whose first non-space content is ```mermaid
case "$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//')" in
'```mermaid'*)
in_block=1
fence_index=$((fence_index + 1))
block_start=$lineno
block_header=""
block_header_line=0
content_lines=0
comment_lines=0
body_first=""
body_first_line=0
decl=""
;;
esac
continue
fi
# Inside a block: a bare closing fence ends it.
case "$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//')" in
'```')
# ── classify the block that just closed ──────────────────
if [ "$content_lines" -eq 0 ]; then
# The block carries no diagram content — mermaid strips
# comments and directives before it parses, so a block
# holding only those is, to mermaid, an empty block: its
# own parser refuses it with "No diagram type detected".
# The block therefore cannot render, and HIGH stays the
# honest severity (HIGH = the block cannot render) — this
# is a real syntax break, not a false one. A MEDIUM
# "nothing verifiable here" would misreport a block that
# mermaid is certain about.
local empty_msg="mermaid block has no content"
if [ "$comment_lines" -gt 0 ]; then
empty_msg="mermaid block holds only comments or directives — no diagram content to render"
fi
emit HIGH M004-empty-block "$file" "$block_start" "$fence_index" \
"$empty_msg" \
"\`\`\`mermaid" \
"add the diagram body, e.g. \`\`\`mermaid / flowchart TD / A[Start] --> B[End] / \`\`\`"
else
local type_kw
type_kw=$(printf '%s' "$block_header" | awk '{print $1}')
if [ -z "$type_kw" ]; then
emit HIGH M003-missing-diagram-type "$file" "$block_header_line" "$fence_index" \
"mermaid block declares no diagram type" \
"$block_header" \
"start the block with a type keyword, e.g. \`flowchart TD\`"
elif ! has_word "$KNOWN_TYPES" "$type_kw"; then
emit MEDIUM M002-unknown-diagram-type "$file" "$block_header_line" "$fence_index" \
"unrecognised diagram type '$type_kw' — not verifiable by this rule set" \
"$block_header" \
"confirm the type is valid mermaid; this rule set cannot vouch for it"
else
if has_word "$MANDATED_FLOWCHART_TYPES" "$type_kw" && [ "$type_kw" = "graph" ]; then
emit MEDIUM M009-non-mandated-keyword "$file" "$block_header_line" "$fence_index" \
"'graph' renders but the product mandate requires 'flowchart'" \
"$block_header" \
"use \`flowchart${block_header#graph}\` instead"
fi
if [ "$content_lines" -le 1 ]; then
# A declared type and nothing else: the block
# carries no statement at all. mermaid renders it
# (an empty chart), so it is NOT a syntax break —
# MEDIUM, never HIGH. The count is driven by the
# *content* lines (the type declaration plus its
# statements), so a trailing blank line or a
# trailing comment cannot change the verdict: the
# block is reported the same whether its body is
# empty, blank or comment-only.
emit MEDIUM M005-empty-body "$file" "$block_header_line" "$fence_index" \
"the block declares the '$type_kw' type but carries no statement" \
"$block_header" \
"add at least one statement for this type, e.g. \`A[Start] --> B[End]\`, \`Task : a1, 2026-01-01, 3d\`, \`participant A\`, \`CUSTOMER ||--o{ ORDER : places\`"
else
# A first statement exists: the per-type rules judge
# whether it is well-formed. A malformed first
# statement is a real syntax break (HIGH).
case "$type_kw" in
gantt)
local gantt_head
gantt_head=$(first_word "$body_first")
if has_word "$GANTT_KEYS" "$gantt_head"; then
:
elif printf '%s' "$body_first" | grep -qE "$GANTT_TASK_RE"; then
:
elif printf '%s' "$body_first" | grep -qE "$GANTT_TASK_LOOSE_RE"; then
emit MEDIUM M006-malformed-gantt-header "$file" "$body_first_line" "$fence_index" \
"gantt block's first statement looks like a task line but is incomplete" \
"$body_first" \
"write the task as \`<name> : <id>, <start>[, <end|duration>]\`"
else
emit HIGH M006-malformed-gantt-header "$file" "$body_first_line" "$fence_index" \
"gantt block's first statement is neither a gantt key nor a task line" \
"$body_first" \
"start with a gantt key (e.g. \`dateFormat YYYY-MM-DD\`) or a task line (e.g. \`Task : a1, 2024-01-01, 3d\`)"
fi
;;
sequenceDiagram)
local ok=false head
head=$(first_word "$body_first")
if has_word "$SEQ_KEYS" "$head" || printf '%s' "$body_first" | grep -qE '^[A-Za-z0-9_]+[[:space:]]*(->>|-->>|->|-->|-\)|--)'; then
ok=true
fi
if [ "$ok" = false ]; then
emit HIGH M007-malformed-sequence-diagram "$file" "$body_first_line" "$fence_index" \
"sequenceDiagram block has no participant or message statement" \
"$body_first" \
"add a participant or message, e.g. \`participant A\` or \`A->>B: hello\`"
fi
;;
erDiagram)
# A relation, an attribute block, or a bare
# entity list. mermaid parses a bare
# identifier list as isolated entities and
# renders it, so "the first statement is not
# a relation" is NOT a syntax break — that
# false HIGH is what made this rule
# untrustworthy. What genuinely breaks the
# parse is a name carrying characters
# outside mermaid's entity-name grammar.
#
# Relation test = the full mermaid v2
# arrow grammar: left cardinality
# (|o zero-or-one, || exactly-one, }o
# zero-or-more, }| one-or-more), arrow
# (-- or ..), right cardinality (o|, ||,
# o{, {|). An earlier partial pattern
# (|[{}]|}|o|o{|}--|..) missed every
# brace-less arrow, so `A ||--|| B` and
# `A ||--| B` fell through to the
# entity-name check and fired a false
# HIGH on valid one-to-one relations.
if printf '%s' "$body_first" | grep -qE '(\|o|\|\||\}o|\}\|)(--|\.\.)(o\||\|\||o\{|\|\{)|[{}]'; then
:
elif printf '%s' "$body_first" | grep -qE '[#@!?%():;,+=~|<>`'"'"']'; then
emit HIGH M008-malformed-entity-diagram "$file" "$body_first_line" "$fence_index" \
"erDiagram entity name carries a character mermaid rejects" \
"$body_first" \
"entity names take letters, digits, \`_\`, \`-\`, \`.\` and spaces — write a relation as \`CUSTOMER ||--o{ ORDER : places\`"
fi
;;
esac
fi
fi
fi
in_block=0
continue
;;
esac
# Content line inside the block.
if is_comment "$line"; then
# A comment (`%% …`) or a directive (`%%{ … }%%`) is not content:
# mermaid strips it before it parses, so it is neither the block's
# type declaration nor one of its statements. It is counted on its
# own (comment_lines) and never as content, so it can neither
# pass for a statement nor change the empty-body verdict.
comment_lines=$((comment_lines + 1))
continue
fi
if [ -n "$(printf '%s' "$line" | tr -d '[:space:]')" ]; then
content_lines=$((content_lines + 1))
if [ "$block_header_line" -eq 0 ]; then
# First content line is the type declaration — a comment
# ahead of it is skipped, so a leading comment never reads
# as the diagram's type.
block_header_line=$lineno
block_header="$line"
decl=$(printf '%s' "$line" | awk '{print $1}')
elif [ -z "$body_first" ]; then
# First statement after the type declaration — comments in
# between are skipped, so the first *statement* is read, not
# the first line that happens to follow the type.
body_first="$line"
body_first_line=$lineno
fi
fi
done < "$file"
# ── unterminated block ───────────────────────────────────────────────────
if [ "$in_block" -eq 1 ]; then
emit HIGH M001-fence-not-closed "$file" "$block_start" "$fence_index" \
"mermaid block opened at line $block_start is never closed" \
"\`\`\`mermaid" \
"close the block with a bare \`\`\` fence"
fi
}
for f in "${FILES[@]}"; do
lint_file "$f" || exit 3
done
if [ "$TSV" != true ]; then
if [ "$FINDINGS" -eq 0 ]; then
echo "mermaid-lint: no findings in ${#FILES[@]} file(s)"
else
echo "mermaid-lint: $FINDINGS finding(s) — HIGH=$HIGH MEDIUM=$MEDIUM"
fi
fi
if [ "$HIGH" -gt 0 ]; then exit 1; fi
if [ "$MEDIUM" -gt 0 ]; then exit 2; fi
exit 0
scripts/bash/setup-architect.sh
#!/usr/bin/env bash
set -e
# JSON output always goes through jq -n (no manual string building).
JSON_MODE=false
ACTION=""
ARGS=()
VIEWS="core"
ADR_HEURISTIC="surprising"
DECOMPOSE=true
PREVIEW=false
# Get script directory FIRST (needed for common.sh sourcing)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Find project root by walking up from CWD
_find_project_root() {
local dir="$(pwd)"
while [ "$dir" != "/" ]; do
if [ -d "$dir/.adlc" ] || [ -d "$dir/.git" ]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}
PROJECT_ROOT="$(_find_project_root)" || PROJECT_ROOT="$(pwd)"
# Load common functions - use absolute path from project root
if [[ -n "$PROJECT_ROOT" && -f "$PROJECT_ROOT/.adlc/scripts/bash/common.sh" ]]; then
source "$PROJECT_ROOT/.adlc/scripts/bash/common.sh"
elif [[ -f "$SCRIPT_DIR/common.sh" ]]; then
source "$SCRIPT_DIR/common.sh"
else
echo "Error: Could not find common.sh" >&2
exit 1
fi
# Get all paths and variables from common functions (parse, never eval:
# eval would execute path content as shell code if it contained $(...) or quotes)
REPO_ROOT=$(get_feature_paths | sed -n 's/^REPO_ROOT="\(.*\)"$/\1/p')
if [[ -z "${REPO_ROOT:-}" ]]; then
echo "Error: could not resolve repository root" >&2
exit 1
fi
# Layout roots (.adlc/ machine pipeline; docs/adlc/ human-facing deliverables)
ADLC_DIR="${ADLC_DIR:-.adlc}"
DOCS_ADLC_DIR="${DOCS_ADLC_DIR:-docs/adlc}"
DOCS_ADLC_MEMORY="${DOCS_ADLC_MEMORY:-docs/adlc/memory}"
DOCS_ADLC_PRODUCT="${DOCS_ADLC_PRODUCT:-docs/adlc/product}"
DOCS_ADLC_ARCHITECT="${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}"
ADLC_DRAFTS="${ADLC_DRAFTS:-.adlc/drafts}"
# dual-read: resolve a memory-relative artifact to the canonical
# docs/adlc/memory path, falling back to legacy .adlc/memory when only the
# legacy file exists. Writers use the canonical path directly.
_adlc_memory_path() {
local rel="$1"
local docs_path="$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/$rel"
local legacy_path="$REPO_ROOT/.adlc/memory/$rel"
if [[ -e "$docs_path" ]]; then
echo "$docs_path"
else
echo "$legacy_path"
fi
}
# Sourcing contract: `source setup-architect.sh` must define helpers and
# constants ONLY -- the SKILL.md flows source this file and then call
# next_adr_number / next_ad_delta_version / generate_adr_index. The argument
# parse, the default action and the dispatch run only when the file is
# executed (before this guard, a bare source ran the whole default action).
SETUP_ARCHITECT_EXECUTED=1
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
SETUP_ARCHITECT_EXECUTED=0
fi
if [[ "$SETUP_ARCHITECT_EXECUTED" == 1 ]]; then
# Parse arguments (run after common.sh to have REPO_ROOT defined)
while [[ $# -gt 0 ]]; do
case "$1" in
--json)
JSON_MODE=true
shift
;;
--views)
shift
VIEWS="$1"
shift
;;
--views=*)
VIEWS="${1#*=}"
shift
;;
--adr-heuristic)
shift
ADR_HEURISTIC="$1"
shift
;;
--adr-heuristic=*)
ADR_HEURISTIC="${1#*=}"
shift
;;
--no-decompose)
DECOMPOSE=false
shift
;;
--no-decompose=*)
DECOMPOSE=false
shift
;;
--preview)
PREVIEW=true
shift
;;
init|map|update|review|specify|implement|clarify|analyze|validate|plan-dag|execute-dag|summarize)
ACTION="$1"
shift
;;
--help|-h)
echo "Usage: $0 [action] [context] [--json] [--views VIEWS] [--adr-heuristic HEURISTIC]"
echo ""
echo "Actions:"
echo " specify Interactive PRD exploration to create system ADRs (greenfield)"
echo " clarify Refine and resolve ambiguities in existing ADRs"
echo " init Reverse-engineer architecture from existing codebase (brownfield)"
echo " implement Generate full Architecture Description (AD.md) from ADRs"
echo " analyze Validate architecture for consistency and quality issues"
echo " validate Validate plan alignment with architecture (READ-ONLY)"
echo " map (alias for init) Reverse-engineer architecture from existing codebase"
echo " update Update architecture based on code/spec changes"
echo " review Validate architecture against constitution"
echo ""
echo "DAG Workflow Actions (used internally by implement):"
echo " plan-dag Phase 1: Generate DAG execution plan for user approval"
echo " execute-dag Phase 2: Execute DAG to generate views per sub-system"
echo " summarize Phase 3: Aggregate views into unified AD.md"
echo ""
echo "Options:"
echo " --json Output results in JSON format"
echo " --views VIEWS Architecture views to generate: core (default), all, or comma-separated"
echo " --adr-heuristic H ADR generation heuristic: surprising (default), all, minimal"
echo " --no-decompose Disable automatic sub-system decomposition (default: auto-detect)"
echo " --preview Preview only: list planned writes (AD.md, views, promotions) without writing"
echo " --help Show this help message"
echo ""
echo "Examples:"
echo " $0 specify \"B2B SaaS for supply chain management\""
echo " $0 init --views all \"Django monolith with PostgreSQL\""
echo " $0 init --views concurrency,operational \"Microservices architecture\""
echo " $0 clarify --adr-heuristic all \"Document all decisions\""
echo " $0 implement \"Generate full AD.md from ADRs\""
echo ""
echo "Pro Tip: Add context/description after the action for better results."
echo "The AI will use your input to understand system scope and constraints."
exit 0
;;
*)
ARGS+=("$1")
shift
;;
esac
done
# Default action if not specified
if [[ -z "$ACTION" ]]; then
if [[ -f "$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/AD.md" || -f "$REPO_ROOT/AD.md" ]]; then
ACTION="update"
else
ACTION="init"
fi
fi
# Ensure directories exist ( compiled AD lives under docs/adlc/architect)
mkdir -p "$REPO_ROOT/.adlc/memory"
mkdir -p "$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}"
mkdir -p "$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}"
fi # end SETUP_ARCHITECT_EXECUTED (parse + defaults + directory seeding)
# Architecture files (ADR Lifecycle)
AD_FILE="$REPO_ROOT/docs/adlc/architect/AD.md"
TEMPLATE_FILE="$REPO_ROOT/.adlc/templates/architecture-template.md"
AD_TEMPLATE_FILE="$REPO_ROOT/.adlc/templates/AD-template.md"
# Export for use in functions
export ARCHITECTURE_VIEWS="$VIEWS"
export ADR_HEURISTIC="$ADR_HEURISTIC"
export DECOMPOSE="$DECOMPOSE"
# Function to detect sub-systems from codebase structure
detect_subsystems() {
local subsystems=""
local count=0
echo "Detecting sub-systems from codebase structure..." >&2
# Check for common sub-system patterns
# 1. Top-level feature directories (src/, app/, services/)
local dirs=()
if [[ -d "src" ]]; then
for d in src/*/; do
if [[ -d "$d" ]]; then
local dirname
dirname=$(basename "$d")
# Skip common non-sub-system directories
if [[ "$dirname" != "utils" && "$dirname" != "common" && "$dirname" != "lib" && "$dirname" != "shared" && "$dirname" != "core" ]]; then
dirs+=("$dirname")
fi
fi
done
fi
if [[ -d "services" ]]; then
for d in services/*/; do
if [[ -d "$d" ]]; then
local dirname
dirname=$(basename "$d")
dirs+=("$dirname")
fi
done
fi
if [[ -d "modules" ]]; then
for d in modules/*/; do
if [[ -d "$d" ]]; then
local dirname
dirname=$(basename "$d")
dirs+=("$dirname")
fi
done
fi
if [[ -d "apps" ]]; then
for d in apps/*/; do
if [[ -d "$d" ]]; then
local dirname
dirname=$(basename "$d")
dirs+=("$dirname")
fi
done
fi
# 2. Check for docker-compose services (microservices indicator)
if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
local compose_file="docker-compose.yml"
[[ -f "docker-compose.yaml" ]] && compose_file="docker-compose.yaml"
local services=()
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+):[[:space:]]*$ ]]; then
local svc="${BASH_REMATCH[1]}"
# Skip common non-service entries
if [[ "$svc" != "version" && "$svc" != "services" && "$svc" != "networks" && "$svc" != "volumes" ]]; then
services+=("$svc")
fi
fi
done < "$compose_file"
for svc in "${services[@]}"; do
local found=false
for d in "${dirs[@]}"; do
d_lower=$(printf '%s' "$d" | tr '[:upper:]' '[:lower:]')
svc_lower=$(printf '%s' "$svc" | tr '[:upper:]' '[:lower:]')
if [[ "$d_lower" == *"$svc_lower"* ]] || [[ "$svc_lower" == *"$d_lower"* ]]; then
found=true
break
fi
done
if [[ "$found" == "false" ]]; then
dirs+=("$svc")
fi
done
fi
# 3. Check for Node.js workspaces (monorepo indicator)
if [[ -f "package.json" ]]; then
if grep -q '"workspaces"' package.json 2>/dev/null; then
local pkgs
pkgs=$(node -e "try { const p = require('./package.json'); console.log(Object.keys(p.workspaces?.packages || {}).join(' ')); } catch(e) { }" 2>/dev/null || true)
for pkg in $pkgs; do
local dirname
dirname=$(basename "$pkg")
if [[ "$dirname" != "node_modules" ]]; then
dirs+=("$dirname")
fi
done
fi
fi
# 4. Check for Python namespace packages
if [[ -f "pyproject.toml" ]]; then
local pkg_dirs=()
while IFS= read -r -d '' d; do
pkg_dirs+=("$(basename "$d")")
done < <(find . -maxdepth 3 -name "__init__.py" -printf '%h\n' 2>/dev/null | grep -v node_modules | grep -v __pycache__ | sort -u || true)
for pdir in "${pkg_dirs[@]}"; do
if [[ "$pdir" != "." && "$pdir" != "src" ]]; then
dirs+=("$pdir")
fi
done
fi
# Remove duplicates and build output
local unique_dirs=($(printf '%s\n' "${dirs[@]}" | sort -u))
if [[ ${#unique_dirs[@]} -gt 0 ]]; then
echo "Detected potential sub-systems:" >&2
for d in "${unique_dirs[@]}"; do
((count++))
echo " - $d" >&2
done
echo "Total: $count sub-system(s)" >&2
else
echo "No distinct sub-systems detected from directory structure." >&2
fi
# Return as JSON if JSON mode
if $JSON_MODE; then
echo "["
local first=true
for d in "${unique_dirs[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
jq -n --arg d "$d" '{"id":$d,"name":$d,"detection_method":"directory","evidence":("directory: "+$d+"/")}'
done
echo ""
echo "]"
fi
}
# Function to detect tech stack from codebase
detect_tech_stack() {
local tech_stack=""
echo "Scanning codebase for technology stack..." >&2
# Languages
if [[ -f "package.json" ]]; then
tech_stack+="**Languages**: JavaScript/TypeScript\n"
tech_stack+="**Package Manager**: npm/yarn\n"
fi
if [[ -f "requirements.txt" ]] || [[ -f "setup.py" ]] || [[ -f "pyproject.toml" ]]; then
tech_stack+="**Languages**: Python\n"
if [[ -f "pyproject.toml" ]]; then
tech_stack+="**Package Manager**: pip/poetry/uv\n"
fi
fi
if [[ -f "Cargo.toml" ]]; then
tech_stack+="**Languages**: Rust\n"
tech_stack+="**Package Manager**: Cargo\n"
fi
if [[ -f "go.mod" ]]; then
tech_stack+="**Languages**: Go\n"
tech_stack+="**Package Manager**: go modules\n"
fi
if [[ -f "pom.xml" ]] || [[ -f "build.gradle" ]]; then
tech_stack+="**Languages**: Java\n"
if [[ -f "pom.xml" ]]; then
tech_stack+="**Build System**: Maven\n"
else
tech_stack+="**Build System**: Gradle\n"
fi
fi
if [[ -f "*.csproj" ]] || [[ -f "*.sln" ]]; then
tech_stack+="**Languages**: C#/.NET\n"
tech_stack+="**Build System**: dotnet\n"
fi
# Frameworks (basic detection)
if [[ -f "package.json" ]]; then
if grep -q "react" package.json 2>/dev/null; then
tech_stack+="**Frontend Framework**: React\n"
fi
if grep -q "vue" package.json 2>/dev/null; then
tech_stack+="**Frontend Framework**: Vue\n"
fi
if grep -q "angular" package.json 2>/dev/null; then
tech_stack+="**Frontend Framework**: Angular\n"
fi
if grep -q "express" package.json 2>/dev/null; then
tech_stack+="**Backend Framework**: Express\n"
fi
if grep -q "fastify" package.json 2>/dev/null; then
tech_stack+="**Backend Framework**: Fastify\n"
fi
fi
if [[ -f "requirements.txt" ]] || [[ -f "pyproject.toml" ]]; then
if grep -q "django" requirements.txt 2>/dev/null || grep -q "django" pyproject.toml 2>/dev/null; then
tech_stack+="**Backend Framework**: Django\n"
fi
if grep -q "fastapi" requirements.txt 2>/dev/null || grep -q "fastapi" pyproject.toml 2>/dev/null; then
tech_stack+="**Backend Framework**: FastAPI\n"
fi
if grep -q "flask" requirements.txt 2>/dev/null || grep -q "flask" pyproject.toml 2>/dev/null; then
tech_stack+="**Backend Framework**: Flask\n"
fi
fi
# Databases
if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
if grep -q "postgres" docker-compose.* 2>/dev/null; then
tech_stack+="**Database**: PostgreSQL\n"
fi
if grep -q "mysql" docker-compose.* 2>/dev/null; then
tech_stack+="**Database**: MySQL\n"
fi
if grep -q "mongodb" docker-compose.* 2>/dev/null; then
tech_stack+="**Database**: MongoDB\n"
fi
if grep -q "redis" docker-compose.* 2>/dev/null; then
tech_stack+="**Cache**: Redis\n"
fi
fi
# Infrastructure
if [[ -f "Dockerfile" ]]; then
tech_stack+="**Containerization**: Docker\n"
fi
local yaml_files=(*.yaml)
if [[ -d "kubernetes" ]] || [[ -d "k8s" ]] || [[ -f "${yaml_files[0]}" ]] && grep -q "apiVersion:" *.yaml 2>/dev/null; then
tech_stack+="**Orchestration**: Kubernetes\n"
fi
if [[ -d "terraform" ]] || [[ -f "*.tf" ]]; then
tech_stack+="**IaC**: Terraform\n"
fi
local github_yml_files=(".github/workflows/"*.yml)
local github_yaml_files=(".github/workflows/"*.yaml)
if [[ -f "${github_yml_files[0]}" ]] || [[ -f "${github_yaml_files[0]}" ]]; then
tech_stack+="**CI/CD**: GitHub Actions\n"
fi
if [[ -f ".gitlab-ci.yml" ]]; then
tech_stack+="**CI/CD**: GitLab CI\n"
fi
if [[ -f "Jenkinsfile" ]]; then
tech_stack+="**CI/CD**: Jenkins\n"
fi
echo -e "$tech_stack"
}
# Function to map directory structure
map_directory_structure() {
echo "Scanning directory structure..." >&2
local structure=""
# Common patterns
if [[ -d "src" ]]; then
structure+="**Source Code**: src/\n"
if [[ -d "src/api" ]] || [[ -d "src/routes" ]]; then
structure+=" - API Layer: src/api/ or src/routes/\n"
fi
if [[ -d "src/services" ]]; then
structure+=" - Business Logic: src/services/\n"
fi
if [[ -d "src/models" ]]; then
structure+=" - Data Models: src/models/\n"
fi
if [[ -d "src/utils" ]]; then
structure+=" - Utilities: src/utils/\n"
fi
fi
if [[ -d "tests" ]] || [[ -d "test" ]]; then
structure+="**Tests**: tests/ or test/\n"
fi
if [[ -d "docs" ]]; then
structure+="**Documentation**: docs/\n"
fi
if [[ -d "scripts" ]]; then
structure+="**Scripts**: scripts/\n"
fi
if [[ -d "infra" ]] || [[ -d "infrastructure" ]]; then
structure+="**Infrastructure**: infra/ or infrastructure/\n"
fi
echo -e "$structure"
}
# Function to extract API endpoints (basic pattern matching)
extract_api_endpoints() {
echo "Scanning for API endpoints..." >&2
local endpoints=""
# Look for common API route patterns
if [[ -d "src" ]]; then
# Express.js style
endpoints+=$(grep -r "router\.\(get\|post\|put\|delete\|patch\)" src 2>/dev/null | head -10 || true)
# FastAPI style
endpoints+=$(grep -r "@app\.\(get\|post\|put\|delete\|patch\)" src 2>/dev/null | head -10 || true)
# Flask style
endpoints+=$(grep -r "@app\.route" src 2>/dev/null | head -10 || true)
fi
if [[ -n "$endpoints" ]]; then
echo "API Endpoints detected (sample):"
echo "$endpoints" | head -10
fi
}
# Function to scan existing docs for deduplication
scan_existing_docs() {
local repo_root="${1:-$REPO_ROOT}"
local findings=""
echo "Scanning existing documentation for deduplication..." >&2
# Check for existing architecture docs
if [[ -f "$repo_root/AD.md" ]]; then
findings+="EXISTING_AD: $repo_root/AD.md\n"
fi
if [[ -f "$repo_root/docs/architecture.md" ]]; then
findings+="EXISTING_ARCHITECTURE: $repo_root/docs/architecture.md\n"
fi
# Scan README for tech stack
if [[ -f "$repo_root/README.md" ]]; then
if grep -q "Tech Stack\|Technology\|Built with" "$repo_root/README.md" 2>/dev/null; then
findings+="TECH_STACK_IN_README: $repo_root/README.md\n"
fi
if grep -q "PostgreSQL\|MySQL\|MongoDB\|Redis" "$repo_root/README.md" 2>/dev/null; then
findings+="DATABASE_IN_README: $repo_root/README.md\n"
fi
if grep -q "React\|Vue\|Angular" "$repo_root/README.md" 2>/dev/null; then
findings+="FRONTEND_IN_README: $repo_root/README.md\n"
fi
fi
# Scan AGENTS.md for context
if [[ -f "$repo_root/AGENTS.md" ]]; then
findings+="AGENTS_CONTEXT: $repo_root/AGENTS.md\n"
fi
# Scan CONTRIBUTING.md for dev guidelines
if [[ -f "$repo_root/CONTRIBUTING.md" ]]; then
findings+="DEV_GUIDELINES: $repo_root/CONTRIBUTING.md\n"
fi
echo -e "$findings"
}
# Function to parse views flag
parse_views() {
local views_arg="$1"
case "$views_arg" in
"all"|"full")
echo "context functional information concurrency development deployment operational"
;;
"core"|"minimal"|"")
echo "context functional information development deployment"
;;
*)
# Parse comma-separated: "concurrency,operational"
local valid_views=""
local all_views="context functional information concurrency development deployment operational"
IFS=',' read -ra VIEWS_ARRAY <<< "$views_arg"
for view in "${VIEWS_ARRAY[@]}"; do
view=$(echo "$view" | tr -d ' ') # Trim whitespace
# Check if view is valid
if echo "$all_views" | grep -qw "$view"; then
valid_views="$valid_views $view"
fi
done
# Always include core views
for view in context functional information development deployment; do
if ! echo "$valid_views" | grep -qw "$view"; then
valid_views="$valid_views $view"
fi
done
echo "$valid_views" | sed 's/^ *//'
;;
esac
}
# ============================================================================
# Hybrid ADR Storage Helpers (v2.2.0)
# ============================================================================
# ADRs are always stored as individual files (ADR-{NNN}.md) in a directory
detect_adr_format() {
echo "hybrid"
}
# Parse a YAML frontmatter field from a markdown file (MADR 3.0.0 frontmatter).
# Usage: parse_fm_field "file" "fieldname"
# Returns the field value with quotes, inline comments, and surrounding whitespace stripped.
parse_fm_field() {
local file="$1"
local field="$2"
awk -v fld="^[[:space:]]*$2:[[:space:]]*" '
/^---[[:space:]]*$/ { fm++; next }
fm == 1 && $0 ~ fld {
# strip the "key:" prefix
sub(fld, "")
# strip inline YAML comments
sub(/[[:space:]]+#.*$/, "")
# strip surrounding quotes
gsub(/^["'\'']|["'\'']$/, "")
# strip list brackets / leading "- "
gsub(/^\[|\]$/, "")
# trim
sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+$/, "")
print
exit
}
' "$file"
}
# Extract the MADR H1 title (first "# " line after frontmatter) from a markdown file.
# Usage: parse_fm_title "file"
parse_fm_title() {
awk '/^---[[:space:]]*$/ { fm++; next } fm >= 2 && /^#[[:space:]]+/ { sub(/^#[[:space:]]+/, ""); sub(/[[:space:]]+$/, ""); print; exit }' "$1"
}
# next_adr_number [ROOT] - the next free ADR ledger slot.
#
# Same contract as next_record_number in product-clarify's pdr-lib.sh: the
# number is the LEADING DIGITS of the filename stem (ADR-003-amendment-2.md is record
# 003; suffixes are never arithmetically parsed), and the maximum spans drafts
# AND published memory so a promoted record's slot is not reissued.
#
# Deliberately not one shared implementation: the source tree is
# skills/<group>/<skill>/ while the installed mirror is flat
# .agents/skills/<skill>/, so no single relative source path resolves in both --
# which is why parse_fm_field is duplicated here and in pdr-lib.sh too.
# tests/unit/test_ledger_slot_numbering.py pins the behaviour in both homes.
next_adr_number() {
local root="${1:-$REPO_ROOT}"
local max=0 f num dir
for dir in "$root/.adlc/drafts/adr" "$root/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr"; do
[[ -d "$dir" ]] || continue
for f in "$dir"/ADR-*.md; do
[[ -f "$f" ]] || continue
num=$(basename "$f" .md | sed -n 's/^ADR-\([0-9]\{1,\}\).*/\1/p')
[[ -n "$num" ]] || continue
((10#$num > max)) && max=$((10#$num))
done
done
printf '%03d' "$((max + 1))"
}
# next_ad_delta_version AD_FILE - the next `## vN.N.N Delta:` heading version.
#
# Deltas bump the MINOR and reset the patch: v13.18.0 -> v13.19.0. A patch
# bump (v13.3.1) means amending the delta that is already there, which is a
# deliberate act, not the next free slot -- so this never emits one.
#
# Companion to next_prd_section/next_req_id/next_feature_number in
# product-clarify's pdr-lib.sh: those scan PRD.md, this scans AD.md. Split for
# the same reason the index generators are -- the source tree is
# skills/<group>/<skill>/ while the installed mirror is flat
# .agents/skills/<skill>/, so no one relative source path resolves in both.
next_ad_delta_version() {
local f="${1:-}"
local major=0 minor=0 patch=0 max=""
if [[ -f "$f" ]]; then
max=$(grep -oE '^## v[0-9]+\.[0-9]+\.[0-9]+ Delta' "$f" \
| sed -E 's/^## v([0-9]+)\.([0-9]+)\.([0-9]+) Delta/\1 \2 \3/' \
| sort -k1,1n -k2,2n -k3,3n | tail -1)
fi
if [[ -n "$max" ]]; then
read -r major minor patch <<< "$max"
fi
printf 'v%s.%s.0' "$major" "$((minor + 1))"
}
# Generate adr.md index from individual ADR files
generate_adr_index() {
local scope="${1:-drafts}"
local adr_dir="$REPO_ROOT/.adlc/$scope/adr"
# dual-read: the memory scope indexes the canonical
# docs/adlc/memory root plus any records still in the legacy .adlc/memory
# root (migration stragglers). The index is written where the records
# live — docs preferred whenever it holds any record, else legacy.
local scan_dirs=("$adr_dir")
if [[ "$scope" == "memory" ]]; then
local docs_adr_dir="$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr"
if compgen -G "$docs_adr_dir/ADR-*.md" >/dev/null 2>&1; then
scan_dirs=("$docs_adr_dir" "$adr_dir")
fi
fi
local index_file="${scan_dirs[0]}/adr.md"
if [[ ! -d "${scan_dirs[0]}" ]]; then
return 0
fi
local index_content="# Architecture Decision Records
## ADR Index
| ID | Sub-System | Decision | Status | Date | Decision Makers | File |
|----|------------|----------|--------|------|------------------|------|
"
local quick_links=$'\n---\n\n## Quick Links\n\n'
# Lexical filename sort (repo names are zero-padded: amendment-before-base
# order falls out naturally). IDs are filename stems verbatim — suffixes
# (e.g. -amendment-2) are preserved, never arithmetically parsed.
local _adr_index_files=""
local _d
for _d in "${scan_dirs[@]}"; do
[[ -d "$_d" ]] || continue
_adr_index_files+="$(ls -1 "$_d"/ADR-*.md 2>/dev/null || true)"$'\n'
done
for f in $(printf '%s' "$_adr_index_files" | LC_ALL=C sort); do
local fname
fname=$(basename "$f")
local stem="${fname%.md}"
local id="${stem#ADR-}"
# Purely numeric stems keep the legacy zero-padded display; suffixed
# stems render verbatim (printf %03d on them printed ADR-000).
local disp="$id"
if [[ "$id" =~ ^[0-9]+$ ]]; then
disp=$(printf "%03d" "$((10#$id))")
fi
# Extract metadata from MADR frontmatter (fallback to empty + defaults)
local title=""
local subsystem=""
local status=""
local date=""
local decision_makers=""
title=$(parse_fm_title "$f")
status=$(parse_fm_field "$f" "status")
date=$(parse_fm_field "$f" "date")
decision_makers=$(parse_fm_field "$f" "decision-makers")
subsystem=$(parse_fm_field "$f" "sub-system")
# Defaults
[[ -z "$status" ]] && status="Proposed"
[[ -z "$date" ]] && date="YYYY-MM-DD"
[[ -z "$decision_makers" ]] && decision_makers=""
[[ -z "$subsystem" ]] && subsystem="System"
[[ -z "$title" ]] && title="ADR-$id"
index_content+="| ADR-$disp | $subsystem | $title | $status | $date | $decision_makers | [$fname]($fname) |\n"
quick_links+="- [ADR-$disp: $title]($fname)\n"
done
echo -e "${index_content}\n${quick_links}" > "$index_file"
}
# Read a single ADR by ID
get_adr_by_id() {
local adr_id="$1"
adr_id="${adr_id#ADR-}"
local scope="${2:-drafts}"
local adr_dir="$REPO_ROOT/.adlc/$scope/adr"
# dual-read: the memory scope searches the canonical
# docs/adlc/memory root first, then the legacy .adlc/memory root
# (both are checked per file so migration stragglers are found even
# when the docs dir already exists).
local search_dirs=("$adr_dir")
if [[ "$scope" == "memory" ]]; then
search_dirs=("$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr" "$adr_dir")
fi
# Resolve exact stems first (ADR-<given>.md verbatim); numeric fallback
# only for bare IDs. Never strip suffixes (386-amendment-2 is not 3862).
# Reject path separators outright (the old digit-strip sanitized by
# accident; keep traversal impossible explicitly).
case "$adr_id" in *"/"*|.*) return 1;; esac
local _d hybrid_file
for _d in "${search_dirs[@]}"; do
hybrid_file="$_d/ADR-$adr_id.md"
if [[ -f "$hybrid_file" ]]; then
cat "$hybrid_file"
return 0
fi
if [[ "$adr_id" =~ ^[0-9]+$ ]]; then
local padded_id
padded_id=$(printf "%03d" "$((10#$adr_id))")
hybrid_file="$_d/ADR-$padded_id.md"
if [[ -f "$hybrid_file" ]]; then
cat "$hybrid_file"
return 0
fi
fi
done
return 1
}
# List all ADR IDs
list_adrs() {
local scope="${1:-drafts}"
local adr_dir="$REPO_ROOT/.adlc/$scope/adr"
if [[ "$scope" == "memory" ]]; then
# dual-read: list the canonical docs/adlc/memory root and
# the legacy .adlc/memory root.
local _docs_dir="$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr"
{ ls -1 "$_docs_dir"/ADR-*.md "$adr_dir"/ADR-*.md 2>/dev/null || true; } | sed -E 's#.*/##; s/\.md$//' | LC_ALL=C sort
elif [[ -d "$adr_dir" ]]; then
ls -1 "$adr_dir"/ADR-*.md 2>/dev/null | sed -E 's#.*/##; s/\.md$//' | LC_ALL=C sort
fi
}
# Get ADR count
get_adr_count() {
local scope="${1:-drafts}"
local adr_dir="$REPO_ROOT/.adlc/$scope/adr"
if [[ "$scope" == "memory" ]]; then
# dual-read: sum the canonical docs/adlc/memory root and
# the legacy .adlc/memory root.
local _docs_dir="$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr"
echo $(( $(ls -1 "$_docs_dir"/ADR-*.md 2>/dev/null | wc -l) + $(ls -1 "$adr_dir"/ADR-*.md 2>/dev/null | wc -l) ))
elif [[ -d "$adr_dir" ]]; then
ls -1 "$adr_dir"/ADR-*.md 2>/dev/null | wc -l
else
echo "0"
fi
}
# Write a single ADR to disk (regenerates adr.md index)
write_adr() {
local adr_id="$1"
adr_id="${adr_id#ADR-}"
local adr_content="$2"
local scope="${3:-drafts}"
local adr_dir="$REPO_ROOT/.adlc/$scope/adr"
mkdir -p "$adr_dir"
local target_name
if [[ "$adr_id" =~ ^[0-9]+$ ]]; then
target_name="ADR-$(printf "%03d" "$((10#$adr_id))").md"
else
case "$adr_id" in *"/"*|.*) echo "write_adr: invalid id: $adr_id" >&2; return 1;; esac
target_name="ADR-$adr_id.md"
fi
echo "$adr_content" > "$adr_dir/$target_name"
# Regenerate derived artifacts
generate_adr_index "$scope"
}
# Move ADR from one scope to another (e.g., drafts -> memory)
move_adr() {
local adr_id="$1"
adr_id="${adr_id#ADR-}"
local from_scope="${2:-drafts}"
local to_scope="${3:-memory}"
local from_dir="$REPO_ROOT/.adlc/$from_scope/adr"
local to_dir="$REPO_ROOT/.adlc/$to_scope/adr"
# dual-read: promotion writers write the NEW path only — the memory
# scope targets docs/adlc/memory (readers dual-read the legacy root).
if [[ "$to_scope" == "memory" ]]; then
to_dir="$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr"
fi
case "$adr_id" in *"/"*|.*) echo "move_adr: invalid id: $adr_id" >&2; return 1;; esac
mkdir -p "$to_dir"
# Exact stem first; numeric fallback only for bare IDs.
local src_file="$from_dir/ADR-$adr_id.md"
if [[ ! -f "$src_file" ]] && [[ "$adr_id" =~ ^[0-9]+$ ]]; then
src_file="$from_dir/ADR-$(printf "%03d" "$((10#$adr_id))").md"
fi
if [[ -f "$src_file" ]]; then
mv "$src_file" "$to_dir/$(basename "$src_file")"
else
echo "[ERROR] move_adr: source file not found: $src_file" >&2
return 1
fi
# Regenerate both scopes
generate_adr_index "$from_scope"
generate_adr_index "$to_scope"
}
# ============================================================================
# Diagram generation
# ============================================================================
# Function to generate and insert diagrams into architecture.md
generate_and_insert_diagrams() {
local arch_file="$1"
local system_name="${2:-System}"
local views_list="${3:-$ARCHITECTURE_VIEWS}"
# Dirty-worktree guard (CDR-002): regeneration overwrites uncommitted
# AD.md/content changes. Read-only callers must never trigger it, and
# update actions refuse on a dirty worktree unless explicitly forced
# with FORCE_REGENERATE=1.
if [[ -d "$REPO_ROOT/.git" ]] && command -v git &> /dev/null; then
if ! git -C "$REPO_ROOT" diff --quiet -- "$arch_file" 2>/dev/null \
|| ! git -C "$REPO_ROOT" diff --cached --quiet -- "$arch_file" 2>/dev/null; then
if [[ "${FORCE_REGENERATE:-0}" != "1" ]]; then
echo "❌ Refusing to regenerate diagrams: $arch_file has uncommitted changes." >&2
echo " Commit or stash first, or re-run with FORCE_REGENERATE=1 to overwrite." >&2
return 1
fi
echo "⚠️ FORCE_REGENERATE=1: regenerating despite uncommitted changes to $arch_file" >&2
fi
fi
# Parse views
local parsed_views
parsed_views=$(parse_views "$views_list")
echo "📊 Generating architecture diagrams..." >&2
echo " Views: $parsed_views" >&2
# Get diagram format from config
local diagram_format
diagram_format=$(get_architecture_diagram_format)
echo " Using diagram format: $diagram_format" >&2
# Source diagram generators
local generator_dir="$SCRIPT_DIR"
if [[ "$diagram_format" == "mermaid" ]]; then
source "$generator_dir/mermaid-generator.sh"
else
source "$generator_dir/ascii-generator.sh"
fi
# Generate each diagram and insert into template
for view in $parsed_views; do
echo " Generating ${view} view diagram..." >&2
local diagram_code
if [[ "$diagram_format" == "mermaid" ]]; then
diagram_code=$(generate_mermaid_diagram "$view" "$system_name")
# Validate Mermaid syntax
if ! validate_mermaid_syntax "$diagram_code"; then
echo " ⚠️ Mermaid validation failed for ${view} view, using ASCII fallback" >&2
source "$generator_dir/ascii-generator.sh"
diagram_code=$(generate_ascii_diagram "$view" "$system_name")
diagram_format="ascii"
fi
else
diagram_code=$(generate_ascii_diagram "$view" "$system_name")
fi
# Create the diagram block with appropriate markdown
local diagram_block
if [[ "$diagram_format" == "mermaid" ]]; then
diagram_block="\`\`\`mermaid
$diagram_code
\`\`\`"
else
diagram_block="\`\`\`text
$diagram_code
\`\`\`"
fi
# Insert diagram into the architecture file at appropriate location
# This is a simplified insertion - AI agent via architect.md template will do the real work
# We're just providing the diagram generation capability here
done
echo "✅ Diagram generation complete" >&2
}
# Action: Specify (greenfield - interactive PRD exploration to create ADRs)
action_specify() {
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local adr_template="$REPO_ROOT/.adlc/templates/adr-template.md"
echo "📐 Setting up for interactive ADR creation..." >&2
# Ensure drafts directory exists
mkdir -p "$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}"
# Show decomposition status
if [[ "$DECOMPOSE" == "true" ]]; then
echo "" >&2
echo "🔄 Sub-system decomposition: ENABLED" >&2
echo " (AI agent will detect domains from PRD and propose sub-systems)" >&2
else
echo "" >&2
echo "⚠️ Sub-system decomposition: DISABLED (--no-decompose flag)" >&2
echo " (AI agent will generate monolithic ADRs)" >&2
fi
# Initialize hybrid ADR directory if empty
local adr_count
adr_count=$(get_adr_count "drafts")
if [[ "$adr_count" -eq 0 ]]; then
if [[ -f "$adr_template" ]]; then
echo "Creating ADR template for hybrid storage..." >&2
# Write template as a placeholder ADR-000 that agents will replace
mkdir -p "$adr_dir"
cp "$adr_template" "$adr_dir/ADR-000.md"
echo "✅ Initialized hybrid ADR directory: $adr_dir" >&2
else
mkdir -p "$adr_dir"
echo "✅ Created hybrid ADR directory: $adr_dir" >&2
fi
else
echo "✅ Found $adr_count existing ADR(s)" >&2
fi
echo "" >&2
echo "Ready for interactive PRD exploration." >&2
echo "The AI agent will:" >&2
if [[ "$DECOMPOSE" == "true" ]]; then
echo " 0. (Phase 0) Detect domains in PRD and propose sub-systems" >&2
echo " → Ask user to confirm sub-system breakdown" >&2
fi
echo " 1. Analyze your PRD/requirements input" >&2
echo " 2. Ask clarifying questions about architecture" >&2
echo " 3. Create ADRs for each key decision" >&2
echo " 4. Save decisions to .adlc/drafts/adr/ADR-{NNN}.md (Proposed status)" >&2
echo " (ADRs will be moved to memory/team after /architect-implement)" >&2
if [[ "$DECOMPOSE" == "true" ]]; then
echo " 5. Organize ADRs by sub-system" >&2
fi
echo "" >&2
echo "After completion, run '/architect-implement' to generate full AD.md" >&2
# Ledger slot allocation: hand callers the next free slot (leading digits
# of the filename stem, max across drafts AND published memory -- see
# next_adr_number). Leaving "the highest existing number" to the agent
# reissued taken slots.
local next_adr
next_adr=$(next_adr_number "$REPO_ROOT")
echo "" >&2
echo "Next free ADR slot: ADR-$next_adr" >&2
if $JSON_MODE; then
jq -n \
--arg adr_dir "$adr_dir" \
--arg context "${ARGS[*]}" \
--arg decomposition "$DECOMPOSE" \
--arg next_adr "$next_adr" \
'{status:"success",action:"specify",adr_dir:$adr_dir,context:$context,decomposition:$decomposition,next_adr:$next_adr}'
fi
}
# Action: Clarify (refine existing ADRs)
action_clarify() {
# Auto-migrate both scopes before loading
# Check drafts first (primary working location), fall back to memory if drafts is empty.
# dual-read: the memory fallback prefers docs/adlc/memory and
# falls back to legacy .adlc/memory.
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local fallback_adr_dir="$REPO_ROOT/${DOCS_ADLC_MEMORY:-docs/adlc/memory}/adr"
if [[ ! -d "$fallback_adr_dir" ]]; then
fallback_adr_dir="$REPO_ROOT/.adlc/memory/adr"
fi
local active_dir="$adr_dir"
local active_dir="$adr_dir"
local active_scope="drafts"
local draft_count
draft_count=$(get_adr_count "drafts")
local memory_count
memory_count=$(get_adr_count "memory")
if [[ "$draft_count" -eq 0 ]]; then
if [[ "$memory_count" -gt 0 ]]; then
echo "ℹ️ Drafts ADR file not found, using memory ADRs" >&2
active_dir="$fallback_adr_dir"
active_dir="$fallback_adr_dir"
active_scope="memory"
else
echo "❌ ADR directory does not exist: $adr_dir" >&2
echo "Run '/architect-specify' or '/architect-init' first" >&2
exit 1
fi
fi
local adr_count
adr_count=$(get_adr_count "$active_scope")
local format
format=$(detect_adr_format "$active_scope")
echo "🔍 Loading existing ADRs for clarification..." >&2
echo "Found $adr_count ADR(s) in $active_dir (format: $format)" >&2
echo "" >&2
echo "Ready for ADR refinement." >&2
echo "The AI agent will:" >&2
echo " 1. Review existing ADRs" >&2
echo " 2. Ask targeted clarification questions" >&2
echo " 3. Update ADRs based on your responses" >&2
echo " 4. Regenerate adr.md index after updates" >&2
echo " 5. Flag any inconsistencies or gaps" >&2
if $JSON_MODE; then
jq -n \
--arg adr_dir "$adr_dir" \
--argjson adr_count "$adr_count" \
--arg format "$format" \
--arg context "${ARGS[*]}" \
'{status:"success",action:"clarify",adr_dir:$adr_dir,adr_count:$adr_count,format:$format,context:$context}'
fi
}
# Action: Implement (generate full AD.md from ADRs)
action_implement() {
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local ad_file="$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/AD.md"
local ad_template="$REPO_ROOT/.adlc/templates/AD-template.md"
# Auto-migrate before checking
local adr_count
adr_count=$(get_adr_count "drafts")
if [[ "$adr_count" -eq 0 ]]; then
echo "❌ No ADR drafts found" >&2
echo "Run '/architect-specify' or '/architect-init' first" >&2
exit 1
fi
# Ledger slot: the next vN.N.N Delta heading in the existing AD.md.
local next_delta
next_delta=$(next_ad_delta_version "$ad_file")
if [[ "${PREVIEW:-false}" == "true" ]]; then
echo "Preview (no writes): would init $ad_file from template," >&2
echo " generate views per sub-system, move Accepted ADRs" >&2
echo " $adr_dir -> docs/adlc/memory/adr/, regenerate both indexes." >&2
if $JSON_MODE; then
jq -n -c \
--arg adr_dir "$adr_dir" \
--arg ad_file "$ad_file" \
--argjson adr_count "$adr_count" \
--arg context "${ARGS[*]}" \
--arg next_ad_delta_version "$next_delta" \
'{status:"success",action:"implement",preview:true,adr_dir:$adr_dir,ad_file:$ad_file,adr_count:$adr_count,context:$context,next_ad_delta_version:$next_ad_delta_version}'
fi
return 0
fi
echo "📐 Setting up for Architecture Description generation..." >&2
# Initialize AD.md from template if it doesn't exist
if [[ ! -f "$ad_file" ]]; then
if [[ -f "$ad_template" ]]; then
echo "Creating AD.md from template..." >&2
mkdir -p "$(dirname "$ad_file")"
cp "$ad_template" "$ad_file"
echo "✅ Created: $ad_file" >&2
else
echo "⚠️ AD template not found: $ad_template" >&2
echo "The AI agent will create AD.md from scratch" >&2
fi
else
echo "✅ AD.md already exists, will be updated: $ad_file" >&2
fi
echo "" >&2
echo "Ready for Architecture Description generation." >&2
echo "The AI agent will:" >&2
echo " 1. Read all $adr_count ADR(s) from .adlc/drafts/adr/" >&2
echo " 2. Generate 7 Rozanski & Woods viewpoints" >&2
echo " 3. Apply Security and Performance perspectives" >&2
echo " 4. Create Mermaid diagrams for each view" >&2
echo " 5. Write complete AD.md to docs/adlc/architect/" >&2
echo " 6. Move Accepted ADRs to canonical location (docs/adlc/memory/adr/)" >&2
echo " 7. Regenerate adr.md index for both scopes" >&2
echo " 8. Clean up drafts if all ADRs are Accepted" >&2
if $JSON_MODE; then
jq -n \
--arg adr_dir "$adr_dir" \
--arg ad_file "$ad_file" \
--argjson adr_count "$adr_count" \
--arg context "${ARGS[*]}" \
--arg next_ad_delta_version "$next_delta" \
'{status:"success",action:"implement",adr_dir:$adr_dir,ad_file:$ad_file,adr_count:$adr_count,context:$context,next_ad_delta_version:$next_ad_delta_version}'
fi
}
# Action: Initialize (brownfield - reverse-engineer from codebase, ADRs only)
action_init() {
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local adr_template="$REPO_ROOT/.adlc/templates/adr-template.md"
echo "🔍 Initializing brownfield architecture discovery..." >&2
# Ensure drafts directory exists
mkdir -p "$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}"
# Scan existing docs for deduplication
local existing_docs
existing_docs=$(scan_existing_docs "$REPO_ROOT")
if [[ -n "$existing_docs" ]]; then
echo "📋 Found existing documentation:" >&2
echo "$existing_docs" | while read -r line; do
echo " - $line" >&2
done
echo "" >&2
fi
# Detect tech stack for context
echo "🔍 Scanning codebase..." >&2
local tech_stack
tech_stack=$(detect_tech_stack)
local dir_structure
dir_structure=$(map_directory_structure)
# Phase 0: Sub-system detection (if decomposition enabled)
local subsystems_json=""
local decompose_status="disabled"
if [[ "$DECOMPOSE" == "true" ]]; then
echo "" >&2
echo "🔄 Phase 0: Sub-System Detection" >&2
subsystems_json=$(detect_subsystems)
decompose_status="enabled"
if [[ -n "$subsystems_json" ]] && [[ "$subsystems_json" != "[]" ]]; then
echo "" >&2
echo "📦 Sub-systems will be used to organize ADRs" >&2
echo " (AI agent will confirm with user before proceeding)" >&2
fi
else
echo "" >&2
echo "⚠️ Sub-system decomposition disabled (--no-decompose flag)" >&2
fi
# Initialize hybrid ADR directory if empty
local adr_count
adr_count=$(get_adr_count "drafts")
if [[ "$adr_count" -eq 0 ]]; then
mkdir -p "$adr_dir"
if [[ -f "$adr_template" ]]; then
echo "Creating ADR template for hybrid storage..." >&2
cp "$adr_template" "$adr_dir/ADR-000.md"
echo "✅ Initialized hybrid ADR directory: $adr_dir" >&2
else
echo "✅ Created hybrid ADR directory: $adr_dir" >&2
fi
else
echo "✅ Found $adr_count existing ADR(s)" >&2
fi
echo "" >&2
echo "📊 Codebase Analysis Summary:" >&2
echo "$tech_stack" >&2
echo "" >&2
echo "$dir_structure" >&2
echo "" >&2
echo "Ready for brownfield architecture discovery." >&2
echo "The AI agent will:" >&2
if [[ "$DECOMPOSE" == "true" ]]; then
echo " 0. (Phase 0) Propose sub-systems from code structure" >&2
echo " → Ask user to confirm sub-system breakdown" >&2
fi
echo " 1. Analyze codebase structure and patterns" >&2
echo " 2. Infer architectural decisions from code" >&2
echo " 3. Create ADRs marked as 'Discovered (Inferred)'" >&2
if [[ "$DECOMPOSE" == "true" ]]; then
echo " 4. Organize ADRs by sub-system" >&2
fi
echo " 5. Auto-trigger /architect-clarify to validate findings" >&2
echo "" >&2
echo "NOTE: AD.md will NOT be created until ADRs are validated." >&2
echo " After clarification, run /architect-implement to generate AD.md" >&2
if $JSON_MODE; then
# _sub_raw is a prebuilt JSON array → pass via --argjson.
local _sub_raw="$subsystems_json"
[[ -z "${_sub_raw//[[:space:]]/}" ]] && _sub_raw="[]"
jq -n \
--arg adr_dir "$adr_dir" \
--arg tech_stack "$tech_stack" \
--arg existing_docs "$existing_docs" \
--arg decomposition "$decompose_status" \
--argjson subsystems "$_sub_raw" \
'{status:"success",action:"init",adr_dir:$adr_dir,tech_stack:$tech_stack,existing_docs:$existing_docs,source:"brownfield",decomposition:$decomposition,subsystems:$subsystems}'
fi
}
# Action: Map (brownfield)
action_map() {
echo "🔍 Mapping existing codebase to architecture..." >&2
# Scan existing docs
local existing_docs
existing_docs=$(scan_existing_docs "$REPO_ROOT")
# Detect tech stack
echo "" >&2
echo "Tech Stack Detected:" >&2
tech_stack=$(detect_tech_stack)
echo "$tech_stack" >&2
# Map directory structure
echo "" >&2
echo "Code Organization:" >&2
dir_structure=$(map_directory_structure)
echo "$dir_structure" >&2
# Extract API endpoints
echo "" >&2
extract_api_endpoints >&2
# Output structured data for AI agent to populate AD.md
if $JSON_MODE; then
jq -n \
--arg tech_stack "$tech_stack" \
--arg directory_structure "$dir_structure" \
--arg existing_docs "$existing_docs" \
'{status:"success",action:"map",tech_stack:$tech_stack,directory_structure:$directory_structure,existing_docs:$existing_docs}'
else
echo "" >&2
echo "📋 Mapping complete. Use this information to populate AD.md:" >&2
if [[ -n "$existing_docs" ]]; then
echo "" >&2
echo "Existing Documentation (reference, don't duplicate):" >&2
echo "$existing_docs" | while read -r line; do
echo " - $line" >&2
done
fi
echo "" >&2
echo "Architecture Sections:" >&2
echo " - Context View (3.1): Define system boundaries" >&2
echo " - Development View (3.5): Use directory structure above" >&2
echo " - Deployment View (3.6): Check docker-compose.yml, k8s configs, terraform" >&2
echo " - Functional View (3.2): Use API endpoints detected" >&2
echo " - Information View (3.3): Check database schemas, ORM models" >&2
fi
}
# Action: Update
action_update() {
if [[ ! -f "$AD_FILE" ]]; then
echo "❌ Architecture does not exist: $AD_FILE" >&2
echo "Run '/architect-specify' or '/architect-init' first" >&2
exit 1
fi
echo "🔄 Updating architecture based on recent changes..." >&2
# Check for recent commits (basic approach)
if command -v git &> /dev/null && [[ -d "$REPO_ROOT/.git" ]]; then
echo "" >&2
echo "Recent changes:" >&2
git log --oneline --since="7 days ago" 2>/dev/null | head -10 >&2 || true
fi
# Detect changes in tech stack
echo "" >&2
echo "Current Tech Stack:" >&2
detect_tech_stack >&2
# Regenerate diagrams with current format and views
generate_and_insert_diagrams "$AD_FILE" "System" "$VIEWS"
echo "" >&2
echo "✅ Update analysis complete" >&2
echo "Review the architecture document and update affected sections:" >&2
echo " - New tables/models? → Update Information View" >&2
echo " - New services/components? → Update Functional View + Deployment View" >&2
echo " - New queues/async? → Update Concurrency View (if included)" >&2
echo " - New dependencies? → Update Development View" >&2
echo " - Add ADR if significant decision was made" >&2
if $JSON_MODE; then
jq -n \
--arg ad_file "$AD_FILE" \
--arg adr_dir "$adr_dir" \
'{status:"success",action:"update",ad_file:$ad_file,adr_dir:$adr_dir}'
fi
}
# Action: Review
action_review() {
if [[ ! -f "$AD_FILE" ]]; then
echo "❌ Architecture does not exist: $AD_FILE" >&2
echo "Run '/architect-specify' or '/architect-init' first" >&2
exit 1
fi
echo "🔍 Reviewing architecture..." >&2
# Check for completeness
local issues=()
# Check for required sections
if ! grep -q "## 1\. Introduction" "$AD_FILE"; then
issues+=("Missing: Introduction section")
fi
if ! grep -q "## 2\. Stakeholders & Concerns" "$AD_FILE"; then
issues+=("Missing: Stakeholders section")
fi
if ! grep -q "## 3\. Architectural Views" "$AD_FILE"; then
issues+=("Missing: Architectural Views section")
fi
if ! grep -q "### 3.1 Context View" "$AD_FILE"; then
issues+=("Missing: Context View")
fi
if ! grep -q "### 3.2 Functional View" "$AD_FILE"; then
issues+=("Missing: Functional View")
fi
if ! grep -q "## 4\. Architectural Perspectives" "$AD_FILE"; then
issues+=("Missing: Perspectives section")
fi
if ! grep -q "## 5\. Global Constraints & Principles" "$AD_FILE"; then
issues+=("Missing: Global Constraints section")
fi
# Check for placeholder content
if grep -q "\[SYSTEM_NAME\]" "$AD_FILE"; then
issues+=("Placeholder: System name not filled in")
fi
if grep -q "\[STAKEHOLDER_" "$AD_FILE"; then
issues+=("Placeholder: Stakeholders not filled in")
fi
# Report results
if [[ ${#issues[@]} -eq 0 ]]; then
echo "✅ Architecture review passed - no major issues found" >&2
else
echo "⚠️ Architecture review found issues:" >&2
for issue in "${issues[@]}"; do
echo " - $issue" >&2
done
fi
# Check constitution alignment if it exists (dual-read)
local constitution_file
constitution_file=$(_adlc_memory_path "constitution.md")
if [[ -f "$constitution_file" ]]; then
echo "" >&2
echo "📜 Checking constitution alignment..." >&2
echo "✅ Constitution file found: $constitution_file" >&2
echo "Manually verify that architecture adheres to constitutional principles" >&2
fi
# Check for ADRs (dual-read)
local adr_mem_dir
adr_mem_dir=$(_adlc_memory_path "adr")
if [[ -d "$adr_mem_dir" ]]; then
echo "" >&2
echo "📋 ADR directory found: $adr_mem_dir" >&2
local adr_count
adr_count=$(get_adr_count "memory")
echo " Found $adr_count ADR(s)" >&2
fi
if $JSON_MODE; then
if [[ ${#issues[@]} -eq 0 ]]; then
jq -n \
--arg ad_file "$AD_FILE" \
--arg adr_dir "$adr_mem_dir" \
--argjson issues "[]" \
'{status:"success",action:"review",ad_file:$ad_file,adr_dir:$adr_dir,issues:$issues}'
else
# Format issues as JSON array
issues_json=$(printf '%s\n' "${issues[@]}" | jq -R . | jq -s .)
jq -n \
--arg ad_file "$AD_FILE" \
--arg adr_dir "$adr_mem_dir" \
--argjson issues "$issues_json" \
'{status:"warning",action:"review",ad_file:$ad_file,adr_dir:$adr_dir,issues:$issues}'
fi
fi
}
# Action: Analyze (validate architecture consistency)
action_analyze() {
echo "🔍 Architecture Analysis Mode" >&2
echo ""
# Auto-migrate before analysis
# dual-read: compiled AD at docs/adlc/architect/AD.md, legacy
# repo-root AD.md fallback.
local ad_file="$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/AD.md"
if [[ ! -f "$ad_file" ]]; then ad_file="$REPO_ROOT/AD.md"; fi
# dual-read: canonical docs/adlc/memory first, legacy fallback.
local adr_dir
adr_dir=$(_adlc_memory_path "adr")
local constitution_file
constitution_file=$(_adlc_memory_path "constitution.md")
local ad_exists=false
local adr_exists=false
local constitution_exists=false
if [[ -f "$ad_file" ]]; then
ad_exists=true
echo "📄 AD.md found: $ad_file" >&2
else
echo "⚠️ AD.md not found at $ad_file" >&2
fi
local adr_count
adr_count=$(get_adr_count "memory")
if [[ "$adr_count" -gt 0 ]]; then
adr_exists=true
echo "📋 ADR file found: $adr_dir ($adr_count ADRs)" >&2
else
echo "⚠️ ADR directory not found: $adr_dir" >&2
fi
if [[ -f "$constitution_file" ]]; then
constitution_exists=true
echo "📜 Constitution found: $constitution_file" >&2
fi
# Scan for feature-level architecture
local feature_ads=()
local feature_adrs=()
if [[ -d "$REPO_ROOT/specs" ]]; then
while IFS= read -r -d '' f; do
feature_ads+=("$f")
done < <(find "$REPO_ROOT/specs" -name "AD.md" -print0 2>/dev/null)
while IFS= read -r -d '' f; do
feature_adrs+=("$f")
done < <(find "$REPO_ROOT/specs" -name "ADR-*.md" -print0 2>/dev/null)
if [[ ${#feature_ads[@]} -gt 0 ]]; then
echo "📁 Feature ADs found: ${#feature_ads[@]}" >&2
fi
if [[ ${#feature_adrs[@]} -gt 0 ]]; then
echo "📁 Feature ADRs found: ${#feature_adrs[@]}" >&2
fi
fi
echo "" >&2
echo "Ready for architecture consistency analysis." >&2
echo "The AI agent will:" >&2
echo " 1. Load all architecture artifacts" >&2
echo " 2. Execute detection passes A-G" >&2
echo " 3. Assign severity levels to findings" >&2
echo " 4. Generate structured analysis report" >&2
echo " 5. Suggest remediation actions" >&2
if $JSON_MODE; then
local feature_ads_json="[]"
local feature_adrs_json="[]"
if [[ ${#feature_ads[@]} -gt 0 ]]; then
feature_ads_json=$(printf '%s\n' "${feature_ads[@]}" | jq -R . | jq -s .)
fi
if [[ ${#feature_adrs[@]} -gt 0 ]]; then
feature_adrs_json=$(printf '%s\n' "${feature_adrs[@]}" | jq -R . | jq -s .)
fi
# ad_exists/adr_exists/constitution_exists are literal "true"/"false"
# strings; pass them via --argjson as JSON booleans. feature_*_json
# are prebuilt JSON arrays → pass via --argjson.
local _ad_bool=false _adr_bool=false _const_bool=false
[[ "$ad_exists" == "true" ]] && _ad_bool=true
[[ "$adr_exists" == "true" ]] && _adr_bool=true
[[ "$constitution_exists" == "true" ]] && _const_bool=true
jq -n \
--arg ad_file "$ad_file" \
--argjson ad_exists "$_ad_bool" \
--arg adr_dir "$adr_dir" \
--argjson adr_exists "$_adr_bool" \
--arg constitution_file "$constitution_file" \
--argjson constitution_exists "$_const_bool" \
--argjson feature_ads "$feature_ads_json" \
--argjson feature_adrs "$feature_adrs_json" \
--arg context "${ARGS[*]}" \
'{status:"success",action:"analyze",ad_file:$ad_file,ad_exists:$ad_exists,adr_dir:$adr_dir,adr_exists:$adr_exists,constitution_file:$constitution_file,constitution_exists:$constitution_exists,feature_ads:$feature_ads,feature_adrs:$feature_adrs,context:$context}'
fi
}
# Action: Plan DAG (Phase 1 of implement - generate execution plan)
action_plan_dag() {
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
local state_file="$REPO_ROOT/.adlc/architect/state.json"
local views_dir="$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/views"
echo "📐 DAG Planning Phase" >&2
echo "" >&2
# Auto-migrate before planning
local adr_count
adr_count=$(get_adr_count "drafts")
if [[ "$adr_count" -eq 0 ]]; then
echo "❌ No ADR drafts found" >&2
echo "Run '/architect-specify' or '/architect-init' first" >&2
exit 1
fi
# Ensure directories exist (state stays under .adlc/architect; views are
# published under docs/adlc/architect/views per the shared layout).
mkdir -p "$REPO_ROOT/.adlc/architect"
mkdir -p "$views_dir"
# Extract unique sub-systems from ADR files
local subsystems=()
if [[ -d "$adr_dir" ]]; then
for f in "$adr_dir"/ADR-*.md; do
[[ -f "$f" ]] || continue
local subsystem=""
subsystem=$(parse_fm_field "$f" "sub-system")
if [[ -n "$subsystem" && "$subsystem" != "Sub-System" ]]; then
local found=false
for s in "${subsystems[@]}"; do
if [[ "$s" == "$subsystem" ]]; then
found=true
break
fi
done
if [[ "$found" == "false" ]]; then
subsystems+=("$subsystem")
fi
fi
done
elif [[ -d "$adr_dir" ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^\|[[:space:]]*ADR-[0-9]+[[:space:]]*\|[[:space:]]*([^|]+)[[:space:]]*\| ]]; then
local subsystem="${BASH_REMATCH[1]}"
subsystem=$(echo "$subsystem" | xargs)
if [[ -n "$subsystem" && "$subsystem" != "Sub-System" ]]; then
local found=false
for s in "${subsystems[@]}"; do
if [[ "$s" == "$subsystem" ]]; then
found=true
break
fi
done
if [[ "$found" == "false" ]]; then
subsystems+=("$subsystem")
fi
fi
fi
done < "$adr_dir"
fi
# Default to "System" if no sub-systems found
if [[ ${#subsystems[@]} -eq 0 ]]; then
subsystems=("System")
fi
echo "📋 ADR directory found: $adr_dir" >&2
echo " Found $adr_count ADR(s)" >&2
echo " Sub-systems detected: ${subsystems[*]}" >&2
echo "" >&2
echo "Ready for DAG planning." >&2
echo "The AI agent will:" >&2
echo " 1. Analyze ADRs by sub-system" >&2
echo " 2. Generate customized DAG per sub-system" >&2
echo " 3. Present execution plan for user approval" >&2
echo " 4. Save approved plan to state.json" >&2
if $JSON_MODE; then
# Build subsystems JSON array
local subsystems_json="["
local first=true
for s in "${subsystems[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
subsystems_json+=","
fi
# Parity with python s.lower().replace(" ","-") for ASCII:
# per-char replacement of literal spaces ONLY (tabs preserved;
# jq -n encodes them correctly). No sed collapsing. Non-ASCII
# case mappings may differ (tr is locale-dependent).
local lower slug
lower=$(printf '%s' "$s" | tr '[:upper:]' '[:lower:]')
slug=${lower// /-}
subsystems_json+=$(jq -n -c --arg id "$slug" --arg name "$s" '{id:$id,name:$name}')
done
subsystems_json+="]"
# subsystems_json is a prebuilt JSON array → pass via --argjson.
jq -n \
--arg adr_dir "$adr_dir" \
--arg state_file "$state_file" \
--arg views_dir "$views_dir" \
--argjson adr_count "$adr_count" \
--argjson subsystems "$subsystems_json" \
--arg context "${ARGS[*]}" \
'{status:"success",action:"plan-dag",adr_dir:$adr_dir,state_file:$state_file,views_dir:$views_dir,adr_count:$adr_count,subsystems:$subsystems,context:$context}'
fi
}
# Action: Execute DAG (Phase 2 of implement - generate views based on state)
action_execute_dag() {
local state_file="$REPO_ROOT/.adlc/architect/state.json"
local views_dir="$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/views"
echo "🔧 DAG Execution Phase" >&2
echo "" >&2
# Check if state file exists
if [[ ! -f "$state_file" ]]; then
echo "❌ No execution plan found: $state_file" >&2
echo "Run '/architect-implement' first to generate and approve a DAG plan" >&2
exit 1
fi
# Ensure views directory exists
mkdir -p "$views_dir"
echo "📄 State file found: $state_file" >&2
echo "📁 Views directory: $views_dir" >&2
echo "" >&2
echo "Ready for DAG execution." >&2
echo "The AI agent will:" >&2
echo " 1. Read execution plan from state.json" >&2
echo " 2. Identify next view(s) to generate" >&2
echo " 3. Generate view with dependency context" >&2
echo " 4. Write to docs/adlc/architect/views/{subsystem}/{view}.md" >&2
echo " 5. Update progress in state.json" >&2
if $JSON_MODE; then
# Output the state file content for the AI agent
if [[ -f "$state_file" ]]; then
local state_content
state_content=$(cat "$state_file")
# state_content is file CONTENT (JSON) → pass via --argjson.
jq -n \
--arg state_file "$state_file" \
--arg views_dir "$views_dir" \
--argjson state "$state_content" \
'{status:"success",action:"execute-dag",state_file:$state_file,views_dir:$views_dir,state:$state}'
else
echo "{\"status\":\"error\",\"action\":\"execute-dag\",\"error\":\"state_file_not_found\"}"
fi
fi
}
# Action: Summarize (Phase 3 of implement - aggregate views into AD.md)
action_summarize() {
local state_file="$REPO_ROOT/.adlc/architect/state.json"
local views_dir="$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/views"
local ad_file="$REPO_ROOT/${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}/AD.md"
local adr_dir="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/adr"
echo "📝 Summarization Phase" >&2
echo "" >&2
# Check if views directory exists and has content
if [[ ! -d "$views_dir" ]]; then
echo "❌ Views directory not found: $views_dir" >&2
echo "Run '/architect-implement' to generate views first" >&2
exit 1
fi
# Count view files
local view_count
view_count=$(find "$views_dir" -name "*.md" -type f 2>/dev/null | wc -l)
if [[ "$view_count" -eq 0 ]]; then
echo "❌ No view files found in $views_dir" >&2
echo "Run '/architect-implement' to generate views first" >&2
exit 1
fi
# List all view files
echo "📁 Views directory: $views_dir" >&2
echo " Found $view_count view file(s):" >&2
find "$views_dir" -name "*.md" -type f | while read -r f; do
echo " - ${f#$views_dir/}" >&2
done
echo "" >&2
echo "Ready for summarization." >&2
echo "The AI agent will:" >&2
echo " 1. Read all view files from docs/adlc/architect/views/" >&2
echo " 2. Detect cross-subsystem conflicts" >&2
echo " 3. Resolve conflicts using ADRs as source of truth" >&2
echo " 4. Aggregate into unified AD.md" >&2
echo " 5. Apply Security and Performance perspectives" >&2
echo " 6. Move Accepted ADRs to canonical location" >&2
if $JSON_MODE; then
# Build list of view files
local views_json="["
local first=true
while IFS= read -r f; do
if [[ "$first" == "true" ]]; then
first=false
else
views_json+=","
fi
local rel_path="${f#$views_dir/}"
views_json+=$(jq -n --arg f "$f" --arg rel "$rel_path" '{"path":$f,"relative":$rel}' | tr -d '\n')
done < <(find "$views_dir" -name "*.md" -type f 2>/dev/null)
views_json+="]"
# views_json is a prebuilt JSON array → pass via --argjson.
jq -n \
--arg state_file "$state_file" \
--arg views_dir "$views_dir" \
--arg ad_file "$ad_file" \
--arg adr_dir "$adr_dir" \
--argjson view_count "$view_count" \
--argjson views "$views_json" \
'{status:"success",action:"summarize",state_file:$state_file,views_dir:$views_dir,ad_file:$ad_file,adr_dir:$adr_dir,view_count:$view_count,views:$views}'
fi
}
# Action: Validate (READ-ONLY architecture validation for plan alignment)
action_validate() {
# dual-read: canonical docs/adlc/memory first, legacy fallback.
local adr_dir
adr_dir=$(_adlc_memory_path "adr")
echo "🔍 Architecture Validation Mode (READ-ONLY)" >&2
echo ""
# Auto-migrate before validation
local adr_count
adr_count=$(get_adr_count "memory")
# Check if architecture exists
if [[ "$adr_count" -eq 0 ]]; then
echo "⏭️ Architecture not found: $adr_dir" >&2
echo " Skipping validation gracefully" >&2
if $JSON_MODE; then
echo "{\"status\":\"skipped\",\"action\":\"validate\",\"reason\":\"architecture_not_found\"}"
fi
exit 0
fi
echo "📋 ADR directory found: $adr_dir" >&2
echo " Found $adr_count ADR(s)" >&2
echo "" >&2
echo "Ready for READ-ONLY architecture validation." >&2
echo "The AI agent will:" >&2
echo " 1. Load architecture from ADRs and AD.md" >&2
echo " 2. Validate plan alignment with architecture" >&2
echo " 3. Identify blocking/high-severity issues" >&2
echo " 4. Report findings (READ-ONLY, no modifications)" >&2
if $JSON_MODE; then
jq -n \
--arg adr_dir "$adr_dir" \
--argjson adr_count "$adr_count" \
--arg context "${ARGS[*]}" \
'{status:"success",action:"validate",adr_dir:$adr_dir,adr_count:$adr_count,context:$context}'
fi
}
# Execute action (execution only -- see the sourcing contract above)
if [[ "$SETUP_ARCHITECT_EXECUTED" == 1 ]]; then
case "$ACTION" in
specify)
action_specify
;;
clarify)
action_clarify
;;
init|map)
action_init
;;
implement)
action_implement
;;
plan-dag)
action_plan_dag
;;
execute-dag)
action_execute_dag
;;
summarize)
action_summarize
;;
analyze)
action_analyze
;;
validate)
action_validate
;;
update)
action_update
;;
review)
action_review
;;
*)
echo "❌ Unknown action: $ACTION" >&2
echo "Use --help for usage information" >&2
exit 1
;;
esac
fi # end SETUP_ARCHITECT_EXECUTED (dispatch)
scripts/powershell/ascii-generator.ps1
#!/usr/bin/env pwsh
# ASCII diagram generator for architecture views
# Generates ASCII art for all 7 Rozanski & Woods architectural views
#
# Core Views (always generated with --views=core):
# - Context, Functional, Information, Development, Deployment
#
# Optional Views (only with --views=all or --views=concurrency,operational):
# - Concurrency (3.4)
# - Operational (3.7)
# Generate Context View diagram (system boundary)
# CRITICAL: System must be shown as a SINGLE BLACKBOX - no internal components
# Only show: Stakeholders (human actors) + External systems (third-party, outside your control)
# DO NOT show: Internal databases, services, caches (those go in Deployment/Functional views)
function New-ContextAscii {
param(
[string]$SystemName = "System"
)
return @'
┌─────────────────────────────────────────────────────────────┐
│ Context View Diagram │
│ (System shown as single blackbox) │
└─────────────────────────────────────────────────────────────┘
STAKEHOLDERS EXTERNAL SYSTEMS
──────────── ────────────────
┌──────────────┐ ┌────────────────┐
│ Users │ │ Payment │
│ (End Users) │ │ Provider │
└──────┬───────┘ │ (External) │
│ └───────┬────────┘
│ Uses │
│ │ Processes
▼ │ payments
┌─────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────┐ │
│ │ │ │
│ │ SYSTEM │◄─────────────────┘
│ │ (This App) │
│ │ │──────────────────┐
│ └─────────────────┘ │
│ ▲ │
│ │ │
└─────────────────────────│───────────────────────────────┘
│ Manages │
┌──────────────┐ │ ▼
│ Admins │──────────┘ ┌────────────────┐
│(Administrators) │ Identity │
└──────────────┘ │ Provider │
│ (External) │
└────────────────┘
Legend:
─────► Data/control flow crossing system boundary
[SYSTEM] = Single blackbox (internal details in Functional/Deployment views)
(External) = Third-party services outside your control
'@
}
# Generate Functional View diagram (component interactions)
function New-FunctionalAscii {
return @'
┌─────────────────────────────────────────────────────────────┐
│ Functional View Diagram │
└─────────────────────────────────────────────────────────────┘
User Request
│
▼
┌──────────────────────┐
│ API Gateway │
└──────────┬───────────┘
│
┏──────┴──────┓
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Authentication│ │ Business │
│ Service │ │ Logic │
└───────┬───────┘ └───────┬───────┘
│ │
└──────────┬───────┘
▼
┌──────────────────────┐
│ Data Access Layer │
└──────────┬───────────┘
│
┏──────┴──────┓
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Database │ │ Cache │
└─────────────┘ └─────────────┘
'@
}
# Generate Information View diagram (data entities)
function New-InformationAscii {
return @'
┌─────────────────────────────────────────────────────────────┐
│ Information View Diagram │
└─────────────────────────────────────────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │1 n │ Order │1 n │OrderItem │
├──────────┤◄────────├──────────┤◄────────├──────────┤
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ email │ │ user_id │ │ order_id │
│ name │ │ status │ │product_id│
│created_at│ │ total │ │ quantity │
└──────────┘ │created_at│ │ price │
└──────────┘ └────┬─────┘
│
│n
▼
┌──────────┐
│ Product │
├──────────┤
│ id (PK) │
│ name │
│ price │
│ sku │
└──────────┘
Key: PK = Primary Key, FK = Foreign Key
1 = One, n = Many, ◄──── = Relationship
'@
}
# Generate Concurrency View diagram (process timeline)
# OPTIONAL VIEW: Only generated when --views=all or --views=concurrency
function New-ConcurrencyAscii {
return @'
┌─────────────────────────────────────────────────────────────┐
│ Concurrency View Diagram │
└─────────────────────────────────────────────────────────────┘
User WebServer AppServer Worker Database
│ │ │ │ │
│─Request─>│ │ │ │
│ │ │ │ │
│ │─Process──>│ │ │
│ │ │ │ │
│ │ │──Query───────────────>│
│ │ │ │ │
│ │ │<──Results─────────────│
│ │ │ │ │
│ │ │─Queue Job─>│ │
│ │ │ │ │
│ │ │ │──Update──>│
│ │<─Response─│ │ │
│ │ │ │<─Done────│
│<─Result─│ │ │ │
│ │ │ │ │
Processes run concurrently:
- Main Request/Response Flow (vertical)
- Background Worker Processing (parallel)
'@
}
# Generate Development View diagram (module dependencies)
function New-DevelopmentAscii {
return @'
┌─────────────────────────────────────────────────────────────┐
│ Development View Diagram │
└─────────────────────────────────────────────────────────────┘
Directory Structure:
project-root/
├── src/
│ ├── api/ ◄─── API Layer (Controllers)
│ │ └── routes/ │
│ │ │ depends on
│ ├── services/ ◄───┘
│ │ └── business/ ◄─── Services Layer
│ │ │
│ ├── repositories/ │ depends on
│ │ └── data/ ◄───┘
│ │ ◄─── Data Access Layer
│ ├── models/ │
│ │ └── entities/ ◄───┘ depends on
│ │ ◄─── Models (Shared)
│ └── utils/
│ └── helpers/ ◄─── Utilities (Shared)
│
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
│
└── docs/
Dependency Rules:
- API → Services → Repositories → Models
- No circular dependencies
- Utilities shared across layers
'@
}
# Generate Deployment View diagram (infrastructure)
function New-DeploymentAscii {
return @'
┌─────────────────────────────────────────────────────────────┐
│ Deployment View Diagram │
└─────────────────────────────────────────────────────────────┘
Internet
│
▼
┌────────────────┐
│ Load Balancer │
│ (ALB/Nginx) │
└────────┬───────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Web Server 1 │ │ Web Server 2 │ │ Web Server 3 │
│ (Public) │ │ (Public) │ │ (Public) │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌────────────────┐
│ App Tier │
│ (Private) │
└────────┬───────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Database │ │ Redis Cache │ │ Object │
│ (Primary) │ │ │ │ Storage │
│ (Private) │ │ (Private) │ │ (S3/Blob) │
└───────┬───────┘ └───────────────┘ └───────────────┘
│
▼
┌───────────────┐
│ Database │
│ (Replica) │
│ (Private) │
└───────────────┘
'@
}
# Generate Operational View diagram (operational workflow)
# OPTIONAL VIEW: Only generated when --views=all or --views=operational
function New-OperationalAscii {
return @'
┌─────────────────────────────────────────────────────────────┐
│ Operational View Diagram │
└─────────────────────────────────────────────────────────────┘
Deployment Workflow:
START
│
▼
┌──────────────┐
│ Run Tests │──┐
│ & Build │ │ Fail
└──────┬───────┘ │
│ Pass │
▼ │
┌──────────────┐ │
│ Deploy to │ │
│ Staging │ │
└──────┬───────┘ │
│ │
▼ │
┌──────────────┐ │
│Run Staging │──┤ Fail
│ Tests │ │
└──────┬───────┘ │
│ Pass │
▼ │
┌──────────────┐ │
│ Manual │──┤ Reject
│ Approval? │ │
└──────┬───────┘ │
│ Approve │
▼ │
┌──────────────┐ │
│ Deploy to │ │
│ Production │ │
└──────┬───────┘ │
│ │
▼ │
┌──────────────┐ │
│ Monitor │ │
│ Health │ │
└──────┬───────┘ │
│ │
┌───┴───┐ │
│Healthy│ │
▼ ▼ │
YES NO │
│ │ │
│ ┌────────┐│
│ │Rollback││
│ └────┬───┘│
│ │ │
▼ ▼ ▼
END Alert Team
Monitoring: Continuous
Backups: Daily automated
On-call: 24/7 rotation
'@
}
# Main function to generate diagram for a specific view
# Usage: New-AsciiDiagram -ViewType "context" -SystemName "System Name"
function New-AsciiDiagram {
param(
[Parameter(Mandatory=$true)]
[ValidateSet('context','functional','information','concurrency','development','deployment','operational')]
[string]$ViewType,
[string]$SystemName = "System"
)
switch ($ViewType) {
'context' { return New-ContextAscii -SystemName $SystemName }
'functional' { return New-FunctionalAscii }
'information' { return New-InformationAscii }
'concurrency' { return New-ConcurrencyAscii }
'development' { return New-DevelopmentAscii }
'deployment' { return New-DeploymentAscii }
'operational' { return New-OperationalAscii }
default {
Write-Error "Unknown view type: $ViewType"
return $null
}
}
}
# Export module members
Export-ModuleMember -Function @(
'New-ContextAscii',
'New-FunctionalAscii',
'New-InformationAscii',
'New-ConcurrencyAscii',
'New-DevelopmentAscii',
'New-DeploymentAscii',
'New-OperationalAscii',
'New-AsciiDiagram'
)
scripts/powershell/common.ps1
# common.ps1 — minimal common helpers for adlc-skills architect-* skills
# (PowerShell twin of bash/common.sh). Bundled with the skill so it works
# standalone, outside the Spec Kit extension system. One behavior, two
# platform scripts; keep in lockstep.
$script:CommonPsDir = $PSScriptRoot
# Layout roots (.adlc/ machine pipeline; docs/adlc/ human-facing deliverables)
$ADLC_DIR = if ($env:ADLC_DIR) { $env:ADLC_DIR } else { ".adlc" }
$DOCS_ADLC_DIR = if ($env:DOCS_ADLC_DIR) { $env:DOCS_ADLC_DIR } else { "docs/adlc" }
$DOCS_ADLC_MEMORY = if ($env:DOCS_ADLC_MEMORY) { $env:DOCS_ADLC_MEMORY } else { "docs/adlc/memory" }
$DOCS_ADLC_PRODUCT = if ($env:DOCS_ADLC_PRODUCT) { $env:DOCS_ADLC_PRODUCT } else { "docs/adlc/product" }
$DOCS_ADLC_ARCHITECT = if ($env:DOCS_ADLC_ARCHITECT) { $env:DOCS_ADLC_ARCHITECT } else { "docs/adlc/architect" }
$ADLC_DRAFTS = if ($env:ADLC_DRAFTS) { $env:ADLC_DRAFTS } else { ".adlc/drafts" }
# Locate the project root by walking up from CWD until we find .adlc or .git.
function Get-ProjectRoot {
$dir = (Get-Location).Path
while ($true) {
if ((Test-Path (Join-Path $dir ".adlc")) -or (Test-Path (Join-Path $dir ".git"))) {
return $dir
}
$parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent
}
# Fallback: current working directory.
return (Get-Location).Path
}
# Seed bundled templates into the project's .adlc/templates/ directory.
# Only copies files when the destination does not exist, so user
# customizations are preserved.
function Seed-Templates {
param($RepoRoot)
if (-not $RepoRoot) { $RepoRoot = Get-ProjectRoot }
$srcDir = Join-Path $PSScriptRoot "../../templates"
if (-not (Test-Path $srcDir)) { return }
$adlcDir = if ($env:ADLC_DIR) { $env:ADLC_DIR } else { ".adlc" }
$destDir = Join-Path $RepoRoot "$adlcDir/templates"
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
foreach ($f in Get-ChildItem -LiteralPath $srcDir) {
$dest = Join-Path $destDir $f.Name
if ($f.PSIsContainer) {
if (-not (Test-Path $dest)) {
Copy-Item -Recurse -LiteralPath $f.FullName -Destination $dest
}
} else {
if (-not (Test-Path $dest)) {
Copy-Item -LiteralPath $f.FullName -Destination $dest
}
}
}
}
# Emulates the get_feature_paths function from the Spec Kit common.sh.
# The architect setup script evals this output to obtain REPO_ROOT.
function Get-FeaturePaths {
$repoRoot = Get-ProjectRoot
Seed-Templates -RepoRoot $repoRoot
return "REPO_ROOT=`"$repoRoot`""
}
# Get architecture diagram format (mermaid or ascii).
# Override via ARCHITECTURE_DIAGRAM_FORMAT env var; defaults to "mermaid".
# Invalid values fall back to "mermaid".
function Get-ArchitectureDiagramFormat {
$format = if ($env:ARCHITECTURE_DIAGRAM_FORMAT) { $env:ARCHITECTURE_DIAGRAM_FORMAT } else { "mermaid" }
if ($format -eq "mermaid" -or $format -eq "ascii") { return $format }
return "mermaid"
}
# Validate Mermaid diagram syntax (lightweight regex validation).
# Returns $true if valid, $false if invalid.
# Args: $MermaidCode - Mermaid code string
function Test-MermaidSyntax {
param([string]$MermaidCode)
if ([string]::IsNullOrWhiteSpace($MermaidCode)) { return $false }
if ($MermaidCode -notmatch '(?m)^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|journey|gitGraph|mindmap|timeline)') {
return $false
}
$openBrackets = ([regex]::Matches($MermaidCode, '\[')).Count
$closeBrackets = ([regex]::Matches($MermaidCode, '\]')).Count
$openParens = ([regex]::Matches($MermaidCode, '\(')).Count
$closeParens = ([regex]::Matches($MermaidCode, '\)')).Count
if ($openBrackets -ne $closeBrackets -or $openParens -ne $closeParens) {
return $false
}
return $true
}
scripts/powershell/mermaid-generator.ps1
#!/usr/bin/env pwsh
# Mermaid diagram generator for architecture views
# Generates Mermaid code for all 7 Rozanski & Woods architectural views
#
# Core Views (always generated with --views=core):
# - Context, Functional, Information, Development, Deployment
#
# Optional Views (only with --views=all or --views=concurrency,operational):
# - Concurrency (3.4)
# - Operational (3.7)
# Generate Context View diagram (system boundary)
# CRITICAL: System must be shown as a SINGLE BLACKBOX - no internal components
# Only show: Stakeholders (human actors) + External systems (third-party, outside your control)
# DO NOT show: Internal databases, services, caches (those go in Deployment/Functional views)
function New-ContextMermaid {
param(
[string]$SystemName = "System"
)
return @'
graph TD
%% Stakeholders (human actors interacting with the system)
Users["👥 Users/Clients"]
Admins["👤 Administrators"]
%% THE SYSTEM - Single blackbox (NO internal components)
System["🏢 System<br/>(This Application)"]
%% External Systems (third-party services outside your control)
ExtPayment["💳 Payment Provider<br/>(External)"]
ExtAuth["🔐 Identity Provider<br/>(External)"]
ExtAPI["🌐 Partner API<br/>(External)"]
%% Stakeholder interactions
Users -->|"Uses"| System
Admins -->|"Manages"| System
%% External system integrations
System -->|"Processes payments"| ExtPayment
System -->|"Authenticates via"| ExtAuth
System -->|"Exchanges data"| ExtAPI
%% Styling
classDef systemNode fill:#f47721,stroke:#333,stroke-width:3px,color:#fff
classDef stakeholderNode fill:#4a9eff,stroke:#333,stroke-width:1px,color:#fff
classDef externalNode fill:#e0e0e0,stroke:#333,stroke-width:1px
class System systemNode
class Users,Admins stakeholderNode
class ExtPayment,ExtAuth,ExtAPI externalNode
'@
}
# Generate Functional View diagram (component interactions)
function New-FunctionalMermaid {
return @'
graph TD
APIGateway["API Gateway"]
AuthService["Authentication<br/>Service"]
BusinessLogic["Business Logic<br/>Layer"]
DataAccess["Data Access<br/>Layer"]
Cache["Cache Layer"]
APIGateway -->|Routes| AuthService
APIGateway -->|Routes| BusinessLogic
AuthService -->|Validates| BusinessLogic
BusinessLogic -->|Queries| DataAccess
BusinessLogic -->|Caches| Cache
DataAccess -->|Reads/Writes| Cache
classDef serviceNode fill:#4a9eff,stroke:#333,stroke-width:2px,color:#fff
classDef dataNode fill:#66c2a5,stroke:#333,stroke-width:2px,color:#fff
class APIGateway,AuthService,BusinessLogic serviceNode
class DataAccess,Cache dataNode
'@
}
# Generate Information View diagram (data entities and relationships)
function New-InformationMermaid {
return @'
erDiagram
User ||--o{ Session : has
User ||--o{ Order : places
Order ||--|{ OrderItem : contains
OrderItem }o--|| Product : references
Product ||--o{ Inventory : tracked_in
User ||--o{ Address : has
Order }o--|| Address : ships_to
User {
int id PK
string email
string name
timestamp created_at
}
Order {
int id PK
int user_id FK
string status
decimal total
timestamp created_at
}
Product {
int id PK
string name
decimal price
string sku
}
'@
}
# Generate Concurrency View diagram (process timeline)
# OPTIONAL VIEW: Only generated when --views=all or --views=concurrency
function New-ConcurrencyMermaid {
return @'
sequenceDiagram
participant User
participant WebServer
participant AppServer
participant Worker
participant Database
User->>WebServer: HTTP Request
WebServer->>AppServer: Process Request
AppServer->>Database: Query Data
Database-->>AppServer: Return Results
par Background Processing
AppServer->>Worker: Queue Background Job
Worker->>Database: Update Records
end
AppServer-->>WebServer: Response
WebServer-->>User: HTTP Response
'@
}
# Generate Development View diagram (module dependencies)
function New-DevelopmentMermaid {
return @'
graph LR
API["🔌 API Layer"]
Services["⚙️ Services Layer"]
Repositories["💾 Repositories"]
Models["📦 Models"]
Utils["🛠️ Utilities"]
API -->|depends on| Services
Services -->|depends on| Repositories
Repositories -->|depends on| Models
Services -->|uses| Utils
API -->|uses| Utils
classDef layerNode fill:#9b59b6,stroke:#333,stroke-width:2px,color:#fff
classDef supportNode fill:#95a5a6,stroke:#333,stroke-width:1px,color:#fff
class API,Services,Repositories layerNode
class Models,Utils supportNode
'@
}
# Generate Deployment View diagram (infrastructure)
function New-DeploymentMermaid {
return @'
graph TB
subgraph "Production Environment"
LB["⚖️ Load Balancer"]
subgraph "Application Tier"
Web1["Web Server 1"]
Web2["Web Server 2"]
Web3["Web Server 3"]
end
subgraph "Data Tier"
DB_Primary["🗄️ Database<br/>Primary"]
DB_Replica["🗄️ Database<br/>Replica"]
Cache["💾 Redis Cache"]
end
subgraph "Storage Tier"
S3["☁️ Object Storage"]
end
end
Internet["🌐 Internet"] -->|HTTPS| LB
LB -->|Distributes| Web1
LB -->|Distributes| Web2
LB -->|Distributes| Web3
Web1 -->|Reads/Writes| DB_Primary
Web2 -->|Reads/Writes| DB_Primary
Web3 -->|Reads/Writes| DB_Primary
DB_Primary -->|Replicates to| DB_Replica
Web1 -->|Caches| Cache
Web2 -->|Caches| Cache
Web3 -->|Caches| Cache
Web1 -->|Stores files| S3
Web2 -->|Stores files| S3
Web3 -->|Stores files| S3
classDef infraNode fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
classDef dataNode fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
class LB,Web1,Web2,Web3 infraNode
class DB_Primary,DB_Replica,Cache,S3 dataNode
'@
}
# Generate Operational View diagram (operational workflow)
# OPTIONAL VIEW: Only generated when --views=all or --views=operational
function New-OperationalMermaid {
return @'
flowchart TD
Start([🚀 Deployment Initiated])
BuildTests{Run Tests<br/>& Build}
BuildSuccess[✅ Build Success]
BuildFail[❌ Build Failed]
Deploy[📦 Deploy to Staging]
StagingTests{Staging Tests<br/>Pass?}
ManualApproval{Manual<br/>Approval?}
ProdDeploy[🎯 Deploy to Production]
Monitor[📊 Monitor Health]
HealthCheck{System<br/>Healthy?}
Rollback[⏪ Rollback]
Alert[🚨 Alert Team]
Complete([✅ Deployment Complete])
Start --> BuildTests
BuildTests -->|Pass| BuildSuccess
BuildTests -->|Fail| BuildFail
BuildSuccess --> Deploy
BuildFail --> Alert
Deploy --> StagingTests
StagingTests -->|Pass| ManualApproval
StagingTests -->|Fail| Alert
ManualApproval -->|Approved| ProdDeploy
ManualApproval -->|Rejected| Alert
ProdDeploy --> Monitor
Monitor --> HealthCheck
HealthCheck -->|Healthy| Complete
HealthCheck -->|Issues| Rollback
Rollback --> Alert
classDef successNode fill:#27ae60,stroke:#333,stroke-width:2px,color:#fff
classDef errorNode fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
classDef processNode fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
class BuildSuccess,Complete successNode
class BuildFail,Alert,Rollback errorNode
class Deploy,ProdDeploy,Monitor processNode
'@
}
# Main function to generate diagram for a specific view
# Usage: New-MermaidDiagram -ViewType "context" -SystemName "System Name"
function New-MermaidDiagram {
param(
[Parameter(Mandatory=$true)]
[ValidateSet('context','functional','information','concurrency','development','deployment','operational')]
[string]$ViewType,
[string]$SystemName = "System"
)
switch ($ViewType) {
'context' { return New-ContextMermaid -SystemName $SystemName }
'functional' { return New-FunctionalMermaid }
'information' { return New-InformationMermaid }
'concurrency' { return New-ConcurrencyMermaid }
'development' { return New-DevelopmentMermaid }
'deployment' { return New-DeploymentMermaid }
'operational' { return New-OperationalMermaid }
default {
Write-Error "Unknown view type: $ViewType"
return $null
}
}
}
# Export module members
Export-ModuleMember -Function @(
'New-ContextMermaid',
'New-FunctionalMermaid',
'New-InformationMermaid',
'New-ConcurrencyMermaid',
'New-DevelopmentMermaid',
'New-DeploymentMermaid',
'New-OperationalMermaid',
'New-MermaidDiagram'
)
scripts/powershell/mermaid-lint.ps1
# mermaid-lint.ps1 — Mermaid syntax validator (the mermaid detection pass) —
# flag + suggest, never write. PowerShell twin of bash/mermaid-lint.sh; one
# behavior, two platform scripts; keep in lockstep.
#
# Scans a markdown artifact for ```mermaid fenced blocks and reports one
# finding per broken block: location (file + line + fence index), severity,
# the rule violated, the offending line, and a minimal suggested patch.
#
# Read-only by contract (the lifecycle write scope): this script never
# writes, moves or reformats an artifact. Remediation stays with
# implement/human.
#
# Severity (the mermaid detection pass):
# HIGH = syntax-break — the block cannot render
# MEDIUM = style — renderable but non-mandated form, a body that
# carries no statement at all, or a type this rule
# set cannot vouch for (never a false HIGH)
#
# Comments and directives:
# `%% …` is a mermaid comment and `%%{ … }%%` is a mermaid directive. Both
# are legal anywhere inside a block and carry no diagram content — mermaid
# strips them before it parses the block. This validator therefore skips
# them wherever it reads a block: they are never the block's type
# declaration and never one of its statements.
#
# Usage:
# mermaid-lint.ps1 [--tsv] <file> [file...]
#
# Output:
# default — one readable block per finding (severity, rule, location,
# offending line, suggested patch)
# --tsv — one tab-separated line per finding, for machine consumption:
# severity \t rule \t file \t line \t fence \t message \t
# offending \t suggestion
# (no JSON: this script builds no JSON by hand, and pulls in no
# runtime dependency — runtime independence)
#
# Exit codes:
# 0 = no findings
# 1 = at least one HIGH (syntax-break) finding
# 2 = only MEDIUM (style) findings
# 3 = usage error (missing file)
$ErrorActionPreference = 'Stop'
$script:ErrorView = 'ConciseView'
function Show-Help {
Write-Output @'
Mermaid syntax validator (the mermaid detection pass) — flag + suggest, never write.
Scans a markdown artifact for ```mermaid fenced blocks and reports one
finding per broken block: location (file + line + fence index), severity,
the rule violated, the offending line, and a minimal suggested patch.
Usage:
mermaid-lint.ps1 [--tsv] <file> [file...]
Output:
default — one readable block per finding (severity, rule, location,
offending line, suggested patch)
--tsv — one tab-separated line per finding, for machine consumption:
severity <TAB> rule <TAB> file <TAB> line <TAB> fence <TAB> message <TAB>
offending <TAB> suggestion
(no JSON: this script builds no JSON by hand, and pulls in no
runtime dependency — runtime independence)
Exit codes:
0 = no findings
1 = at least one HIGH (syntax-break) finding
2 = only MEDIUM (style) findings
3 = usage error (missing file)
'@
}
$TSV = $false
$FILES = [System.Collections.Generic.List[string]]::new()
foreach ($arg in $args) {
switch ([string]$arg) {
'--tsv' { $TSV = $true }
'--help' { Show-Help; exit 0 }
'-h' { Show-Help; exit 0 }
default {
if ([string]$arg -like '-*') {
[Console]::Error.WriteLine("mermaid-lint: unknown option '$arg'")
exit 3
}
$FILES.Add([string]$arg)
}
}
}
if ($FILES.Count -eq 0) {
[Console]::Error.WriteLine('Usage: mermaid-lint.ps1 [--tsv] <file> [file...]')
exit 3
}
# ── rule tables ────────────────────────────────────────────────────────────
# Known mermaid diagram keywords. A block whose first content line is not in
# this set is MEDIUM (unknown-but-plausible -> review prompt), never HIGH.
# `flow` and `chart` are NOT keywords: mermaid spells it `flowchart` (one
# word) or the legacy `graph`. A block opening with `chart TD` (or
# `flow chart TD`) therefore falls through to the MEDIUM M002 branch.
$KNOWN_TYPES = ' flowchart graph gantt journey erDiagram sequenceDiagram classDiagram stateDiagram stateDiagram-v2 pie mindmap timeline quadrantChart gitGraph requirementDiagram C4Context C4Container C4Component C4Dynamic C4Deployment zenuml block-beta sankey-beta packet-beta kanban architecture-beta xychart-beta treemap-beta radar-beta '
# The product mandate (product-implement) requires `flowchart`, not `graph`.
# `graph` still renders, so it is a style finding, not a syntax break.
$MANDATED_FLOWCHART_TYPES = ' flowchart graph '
# gantt statements that are structurally meaningful.
$GANTT_KEYS = ' dateFormat axisFormat excludes includes section todayMarker tickInterval weekday weekend title '
# A gantt task line: "<name> : <id>, <start>[, <end|duration>]". The
# `dateFormat` key is optional in mermaid, so a task line is a valid first
# statement — recognised separately from the keys above.
$GANTT_TASK_RE = '^[^:]+:[^,]+,\s*[^,]+'
# A task-line-shaped statement that is not a complete task line ("<name> :"
# with no id/start): ambiguous, so it is reported MEDIUM, never a false HIGH.
$GANTT_TASK_LOOSE_RE = '^[^:]+:'
# sequenceDiagram statement heads.
#
# The keyword table must track mermaid's own grammar, not an impression of it:
# every head here was checked against `mermaid.parse()` (12.2.0) as a first
# statement. `title`, `autonumber`, `box`, `critical` and `break` are
# documented statements mermaid accepts, so leaving them out turned valid
# blocks into HIGH findings. `done` is deliberately absent — mermaid rejects
# it as a first statement.
$SEQ_KEYS = ' participant actor boundary control entity database collections queue alt else opt loop par and rect activate deactivate note link links over right left of title autonumber box critical break end '
$script:HIGH = 0
$script:MEDIUM = 0
$script:FINDINGS = 0
# Collapse tabs and newlines so a field can never break the line format.
function Flatten([string]$S) {
return ((($S -replace "[`r`n`t]", ' ') -replace ' {2,}', ' ').Trim())
}
# emit <severity> <rule> <file> <line> <fence_index> <message> <offending> <patch>
# Writes to the console stream (not the pipeline) so the caller's return
# value stays a clean status code.
function Emit {
param([string]$Sev, [string]$Rule, [string]$File, [string]$Line, [string]$Idx,
[string]$Msg, [string]$Offending, [string]$Patch)
$script:FINDINGS++
if ($Sev -eq 'HIGH') { $script:HIGH++ } else { $script:MEDIUM++ }
if ($TSV) {
[Console]::Out.WriteLine(($Sev, $Rule, (Flatten $File), $Line, $Idx, (Flatten $Msg), (Flatten $Offending), (Flatten $Patch)) -join "`t")
} else {
[Console]::Out.WriteLine(('{0} {1} {2}:{3} (fence #{4})' -f $Sev, $Rule, $File, $Line, $Idx))
[Console]::Out.WriteLine((' {0}' -f $Msg))
[Console]::Out.WriteLine((' offending: {0}' -f $Offending))
[Console]::Out.WriteLine((' suggestion: {0}' -f $Patch))
}
}
# A known keyword check: " word " membership in a space-delimited table.
function Test-Word([string]$Table, [string]$Word) {
return (' ' + $Table + ' ').Contains(' ' + $Word + ' ')
}
# The first whitespace-delimited token of a line, leading whitespace ignored.
function Get-FirstWord([string]$Line) {
$t = $Line.TrimStart()
if ($t -eq '') { return '' }
return ($t -split '\s+')[0]
}
# Is this line a mermaid comment (`%% …`) or directive (`%%{ … }%%`)?
# The check is on the line's first non-space content: only a line whose own
# first non-space characters are `%%` is a comment. A `%%` *inside* a
# statement (`A --> B %% note`) is not a comment — mermaid rejects that form
# (it is a parse error, not a trailing comment). This rule set does not
# model it: such a line is still read as a statement, and no rule is raised
# for it here.
function Test-Comment([string]$Line) {
return $Line.TrimStart().StartsWith('%%')
}
function Lint-File {
param([string]$File)
if (-not (Test-Path -LiteralPath $File -PathType Leaf)) {
[Console]::Error.WriteLine("mermaid-lint: file not found: $File")
return 1
}
$lineno = 0; $fenceIndex = 0; $inBlock = $false
$blockStart = 0; $blockHeader = ''; $blockHeaderLine = 0
# content_lines counts only the lines that carry diagram content — the
# type declaration and its statements; comment_lines counts the stripped
# comments/directives. A block's verdict is driven by content_lines: a
# blank line and a comment carry no content, so neither of them changes
# the count.
$contentLines = 0; $commentLines = 0; $bodyFirst = ''; $bodyFirstLine = 0
foreach ($rawLine in (Get-Content -LiteralPath $File)) {
$lineno++
$line = [string]$rawLine
if (-not $inBlock) {
# Opening fence: a line whose first non-space content is ```mermaid
if ($line.TrimStart().StartsWith('```mermaid')) {
$inBlock = $true
$fenceIndex++
$blockStart = $lineno
$blockHeader = ''; $blockHeaderLine = 0
$contentLines = 0; $commentLines = 0
$bodyFirst = ''; $bodyFirstLine = 0
}
continue
}
# Inside a block: a bare closing fence ends it.
if ($line.TrimStart() -eq '```') {
# ── classify the block that just closed ──────────────────
if ($contentLines -eq 0) {
# The block carries no diagram content — mermaid strips
# comments and directives before it parses, so a block
# holding only those is, to mermaid, an empty block: its
# own parser refuses it with "No diagram type detected".
# HIGH stays the honest severity (HIGH = the block cannot
# render) — this is a real syntax break, not a false one.
$emptyMsg = 'mermaid block has no content'
if ($commentLines -gt 0) {
$emptyMsg = 'mermaid block holds only comments or directives — no diagram content to render'
}
Emit 'HIGH' 'M004-empty-block' $File "$blockStart" "$fenceIndex" `
"$emptyMsg" `
'```mermaid' `
'add the diagram body, e.g. ```mermaid / flowchart TD / A[Start] --> B[End] / ```'
} else {
$typeKw = Get-FirstWord $blockHeader
if ($typeKw -eq '') {
Emit 'HIGH' 'M003-missing-diagram-type' $File "$blockHeaderLine" "$fenceIndex" `
'mermaid block declares no diagram type' `
"$blockHeader" `
'start the block with a type keyword, e.g. `flowchart TD`'
} elseif (-not (Test-Word $KNOWN_TYPES $typeKw)) {
Emit 'MEDIUM' 'M002-unknown-diagram-type' $File "$blockHeaderLine" "$fenceIndex" `
"unrecognised diagram type '$typeKw' — not verifiable by this rule set" `
"$blockHeader" `
'confirm the type is valid mermaid; this rule set cannot vouch for it'
} else {
if ((Test-Word $MANDATED_FLOWCHART_TYPES $typeKw) -and $typeKw -eq 'graph') {
Emit 'MEDIUM' 'M009-non-mandated-keyword' $File "$blockHeaderLine" "$fenceIndex" `
"'graph' renders but the product mandate requires 'flowchart'" `
"$blockHeader" `
("use ``flowchart" + $blockHeader.Substring(5) + "`` instead")
}
if ($contentLines -le 1) {
# A declared type and nothing else: the block carries
# no statement at all. mermaid renders it (an empty
# chart), so it is NOT a syntax break — MEDIUM, never
# HIGH. The count is driven by the *content* lines,
# so a trailing blank line or a trailing comment
# cannot change the verdict.
Emit 'MEDIUM' 'M005-empty-body' $File "$blockHeaderLine" "$fenceIndex" `
"the block declares the '$typeKw' type but carries no statement" `
"$blockHeader" `
'add at least one statement for this type, e.g. `A[Start] --> B[End]`, `Task : a1, 2026-01-01, 3d`, `participant A`, `CUSTOMER ||--o{ ORDER : places`'
} else {
# A first statement exists: the per-type rules judge
# whether it is well-formed. A malformed first
# statement is a real syntax break (HIGH).
switch ($typeKw) {
'gantt' {
$ganttHead = Get-FirstWord $bodyFirst
if (Test-Word $GANTT_KEYS $ganttHead) {
} elseif ($bodyFirst -match $GANTT_TASK_RE) {
} elseif ($bodyFirst -match $GANTT_TASK_LOOSE_RE) {
Emit 'MEDIUM' 'M006-malformed-gantt-header' $File "$bodyFirstLine" "$fenceIndex" `
"gantt block's first statement looks like a task line but is incomplete" `
"$bodyFirst" `
'write the task as `<name> : <id>, <start>[, <end|duration>]`'
} else {
Emit 'HIGH' 'M006-malformed-gantt-header' $File "$bodyFirstLine" "$fenceIndex" `
"gantt block's first statement is neither a gantt key nor a task line" `
"$bodyFirst" `
'start with a gantt key (e.g. `dateFormat YYYY-MM-DD`) or a task line (e.g. `Task : a1, 2024-01-01, 3d`)'
}
}
'sequenceDiagram' {
$ok = $false
$head = Get-FirstWord $bodyFirst
if ((Test-Word $SEQ_KEYS $head) -or ($bodyFirst -match '^[A-Za-z0-9_]+\s*(->>|-->>|->|-->|-\)|--)')) {
$ok = $true
}
if (-not $ok) {
Emit 'HIGH' 'M007-malformed-sequence-diagram' $File "$bodyFirstLine" "$fenceIndex" `
'sequenceDiagram block has no participant or message statement' `
"$bodyFirst" `
'add a participant or message, e.g. `participant A` or `A->>B: hello`'
}
}
'erDiagram' {
# A relation, an attribute block, or a bare
# entity list. mermaid parses a bare
# identifier list as isolated entities and
# renders it, so "the first statement is not
# a relation" is NOT a syntax break — that
# false HIGH is what made this rule
# untrustworthy. What genuinely breaks the
# parse is a name carrying characters
# outside mermaid's entity-name grammar.
#
# Relation test = the full mermaid v2
# arrow grammar: left cardinality
# (|o zero-or-one, || exactly-one, }o
# zero-or-more, }| one-or-more), arrow
# (-- or ..), right cardinality (o|, ||,
# o{, {|). An earlier partial pattern
# (|[{}]|}|o|o{|}--|..) missed every
# brace-less arrow, so `A ||--|| B` and
# `A ||--| B` fell through to the
# entity-name check and fired a false
# HIGH on valid one-to-one relations.
if ($bodyFirst -match '(\|o|\|\||\}o|\}\|)(--|\.\.)(o\||\|\||o\{|\|\{)|[{}]') {
# relation / attribute block — valid
} elseif ($bodyFirst -match '[#@!?%():;,+=~|<>`''"]') {
Emit 'HIGH' 'M008-malformed-entity-diagram' $File "$bodyFirstLine" "$fenceIndex" `
'erDiagram entity name carries a character mermaid rejects' `
"$bodyFirst" `
'entity names take letters, digits, `_`, `-`, `.` and spaces — write a relation as `CUSTOMER ||--o{ ORDER : places`'
}
}
}
}
}
}
$inBlock = $false
continue
}
# Content line inside the block.
if (Test-Comment $line) {
# A comment (`%% …`) or a directive (`%%{ … }%%`) is not content:
# mermaid strips it before it parses, so it is neither the
# block's type declaration nor one of its statements.
$commentLines++
continue
}
if (($line -replace '\s', '') -ne '') {
$contentLines++
if ($blockHeaderLine -eq 0) {
# First content line is the type declaration — a comment
# ahead of it is skipped, so a leading comment never reads
# as the diagram's type.
$blockHeaderLine = $lineno
$blockHeader = $line
} elseif ($bodyFirst -eq '') {
# First statement after the type declaration — comments in
# between are skipped, so the first *statement* is read, not
# the first line that happens to follow the type.
$bodyFirst = $line
$bodyFirstLine = $lineno
}
}
}
# ── unterminated block ───────────────────────────────────────────────
if ($inBlock) {
Emit 'HIGH' 'M001-fence-not-closed' $File "$blockStart" "$fenceIndex" `
"mermaid block opened at line $blockStart is never closed" `
'```mermaid' `
'close the block with a bare ``` fence'
}
return 0
}
foreach ($f in $FILES) {
$rc = Lint-File $f
if ($rc -ne 0) { exit 3 }
}
if (-not $TSV) {
if ($script:FINDINGS -eq 0) {
Write-Output "mermaid-lint: no findings in $($FILES.Count) file(s)"
} else {
Write-Output "mermaid-lint: $($script:FINDINGS) finding(s) — HIGH=$($script:HIGH) MEDIUM=$($script:MEDIUM)"
}
}
if ($script:HIGH -gt 0) { exit 1 }
if ($script:MEDIUM -gt 0) { exit 2 }
exit 0
scripts/powershell/setup-architect.ps1
# Setup architecture description with Rozanski & Woods methodology
[CmdletBinding()]
param(
[Parameter(Position=0)]
[ValidateSet('init', 'map', 'update', 'review', 'specify', 'clarify', 'implement', 'analyze', 'validate', 'plan-dag', 'execute-dag', 'summarize', '')]
[string]$Action = '',
[Parameter(Position=1, ValueFromRemainingArguments=$true)]
[string[]]$Context,
[string]$Views = 'core',
[string]$AdrHeuristic = 'surprising',
[switch]$NoDecompose,
[switch]$Json,
[switch]$Help
)
$Decompose = -not $NoDecompose
$ErrorActionPreference = 'Stop'
# Layout roots (.adlc/ machine pipeline; docs/adlc/ human-facing deliverables)
$ADLC_DIR = if ($env:ADLC_DIR) { $env:ADLC_DIR } else { ".adlc" }
$DOCS_ADLC_DIR = if ($env:DOCS_ADLC_DIR) { $env:DOCS_ADLC_DIR } else { "docs/adlc" }
$DOCS_ADLC_MEMORY = if ($env:DOCS_ADLC_MEMORY) { $env:DOCS_ADLC_MEMORY } else { "docs/adlc/memory" }
$DOCS_ADLC_PRODUCT = if ($env:DOCS_ADLC_PRODUCT) { $env:DOCS_ADLC_PRODUCT } else { "docs/adlc/product" }
$DOCS_ADLC_ARCHITECT = if ($env:DOCS_ADLC_ARCHITECT) { $env:DOCS_ADLC_ARCHITECT } else { "docs/adlc/architect" }
$ADLC_DRAFTS = if ($env:ADLC_DRAFTS) { $env:ADLC_DRAFTS } else { ".adlc/drafts" }
# dual-read: resolve a memory-relative artifact to the canonical
# docs/adlc/memory path, falling back to legacy .adlc/memory when only the
# legacy path exists. PowerShell mirror of _adlc_memory_path in
# setup-architect.sh; callers pass a path relative to the memory root
# (e.g. "adr", "constitution.md"). Returns the legacy path when neither
# exists so creation sites keep their current behavior.
function Get-AdlcMemoryPath {
param([string]$RepoRoot, [string]$RelPath)
$docsBase = $DOCS_ADLC_MEMORY
if (-not $docsBase) { $docsBase = "docs/adlc/memory" }
$docsPath = Join-Path $RepoRoot (Join-Path $docsBase $RelPath)
$legacyPath = Join-Path $RepoRoot (Join-Path ".adlc/memory" $RelPath)
if (Test-Path $docsPath) { return $docsPath }
return $legacyPath
}
# Get-NextAdrNumber [ROOT] - the next free ADR ledger slot.
#
# PowerShell twin of next_adr_number in setup-architect.sh, same contract as
# Get-NextRecordNumber in product-clarify's pdr-lib.ps1: a record's number is
# the LEADING DIGITS of its filename stem (an amendment stem still counts as
# its base record; suffixes are never arithmetically parsed), and the maximum
# spans drafts AND published memory so a promoted record's slot is not reissued.
function Get-NextAdrNumber {
param([string]$Root = (Get-RepositoryRoot))
$max = 0
$docsBase = $DOCS_ADLC_MEMORY
if (-not $docsBase) { $docsBase = "docs/adlc/memory" }
foreach ($dir in @((Join-Path $Root ".adlc/drafts/adr"), (Join-Path $Root (Join-Path $docsBase "adr")))) {
if (-not (Test-Path $dir)) { continue }
foreach ($f in @(Get-ChildItem -Path $dir -Filter 'ADR-*.md' -File -ErrorAction SilentlyContinue)) {
if ($f.BaseName -cmatch '^ADR-(\d+)') {
$num = [int]$Matches[1]
if ($num -gt $max) { $max = $num }
}
}
}
return ('{0:d3}' -f ($max + 1))
}
if ($Help) {
Write-Output "Usage: ./setup-architect.ps1 [action] [context] [-Views VIEWS] [-AdrHeuristic HEURISTIC] [-Json] [-Help]"
Write-Output ""
Write-Output "Actions:"
Write-Output " specify Interactive PRD exploration to create system ADRs (greenfield)"
Write-Output " clarify Refine and resolve ambiguities in existing ADRs"
Write-Output " init Reverse-engineer architecture from existing codebase (brownfield)"
Write-Output " implement Generate full Architecture Description (AD.md) from ADRs"
Write-Output " analyze Validate architecture for consistency and quality issues"
Write-Output " validate Validate plan alignment with architecture (READ-ONLY)"
Write-Output " map (alias for init) Reverse-engineer architecture from existing codebase"
Write-Output " update Update architecture based on code/spec changes"
Write-Output " review Validate architecture against constitution"
Write-Output ""
Write-Output "DAG Workflow Actions (used internally by implement):"
Write-Output " plan-dag Phase 1: Generate DAG execution plan for user approval"
Write-Output " execute-dag Phase 2: Execute DAG to generate views per sub-system"
Write-Output " summarize Phase 3: Aggregate views into unified AD.md"
Write-Output ""
Write-Output "Options:"
Write-Output " -Views VIEWS Architecture views to generate: core (default), all, or comma-separated"
Write-Output " -AdrHeuristic H ADR generation heuristic: surprising (default), all, minimal"
Write-Output " -NoDecompose Disable automatic sub-system decomposition (default: auto-detect)"
Write-Output " -Json Output results in JSON format"
Write-Output " -Help Show this help message"
Write-Output ""
Write-Output "Examples:"
Write-Output " ./setup-architect.ps1 specify `"B2B SaaS for supply chain management`""
Write-Output " ./setup-architect.ps1 init -Views all `"Django monolith with PostgreSQL`""
Write-Output " ./setup-architect.ps1 init -Views concurrency,operational `"Microservices architecture`""
Write-Output " ./setup-architect.ps1 clarify -AdrHeuristic all `"Document all decisions`""
Write-Output " ./setup-architect.ps1 implement `"Generate full AD.md from ADRs`""
Write-Output ""
Write-Output "Pro Tip: Add context/description after the action for better results."
Write-Output "The AI will use your input to understand system scope and constraints."
exit 0
}
# Get repository root
function Get-RepositoryRoot {
# Try environment variable first
if ($env:REPO_ROOT -and (Test-Path $env:REPO_ROOT)) {
return $env:REPO_ROOT
}
# Fallback: search for repository markers
$currentDir = Get-Location
while ($currentDir -and (Test-Path $currentDir)) {
if ((Test-Path "$currentDir\.git") -or (Test-Path "$currentDir\.adlc")) {
return $currentDir
}
$currentDir = Split-Path $currentDir -Parent
}
throw "Could not determine repository root"
}
# Seed bundled templates into the project's .adlc/templates/ directory.
# Only copies files when the destination does not exist, preserving user customizations.
function Seed-Templates {
param([string]$RepoRoot = (Get-RepositoryRoot))
$scriptDir = Split-Path -Parent $MyInvocation.ScriptName
if (-not $scriptDir) {
$scriptDir = Get-Location
}
$srcDir = Join-Path $scriptDir "..\..\templates"
$destDir = Join-Path $RepoRoot ".adlc\templates"
if (-not (Test-Path $srcDir)) {
return
}
if (-not (Test-Path $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}
Get-ChildItem -Path $srcDir | ForEach-Object {
$destPath = Join-Path $destDir $_.Name
if (-not (Test-Path $destPath)) {
if ($_.PSIsContainer) {
Copy-Item -Path $_.FullName -Destination $destPath -Recurse -Force
} else {
Copy-Item -Path $_.FullName -Destination $destPath -Force
}
}
}
}
# Detect tech stack from codebase
function Get-TechStack {
Write-Host "Scanning codebase for technology stack..." -ForegroundColor Cyan
$techStack = @()
# Languages
if (Test-Path "package.json") {
$techStack += "**Languages**: JavaScript/TypeScript"
$techStack += "**Package Manager**: npm/yarn"
}
if ((Test-Path "requirements.txt") -or (Test-Path "setup.py") -or (Test-Path "pyproject.toml")) {
$techStack += "**Languages**: Python"
if (Test-Path "pyproject.toml") {
$techStack += "**Package Manager**: pip/poetry/uv"
}
}
if (Test-Path "Cargo.toml") {
$techStack += "**Languages**: Rust"
$techStack += "**Package Manager**: Cargo"
}
if (Test-Path "go.mod") {
$techStack += "**Languages**: Go"
$techStack += "**Package Manager**: go modules"
}
if ((Test-Path "pom.xml") -or (Test-Path "build.gradle")) {
$techStack += "**Languages**: Java"
if (Test-Path "pom.xml") {
$techStack += "**Build System**: Maven"
} else {
$techStack += "**Build System**: Gradle"
}
}
if ((Get-ChildItem -Filter "*.csproj" -ErrorAction SilentlyContinue).Count -gt 0 -or
(Get-ChildItem -Filter "*.sln" -ErrorAction SilentlyContinue).Count -gt 0) {
$techStack += "**Languages**: C#/.NET"
$techStack += "**Build System**: dotnet"
}
# Frameworks
if (Test-Path "package.json") {
$packageJson = Get-Content "package.json" -Raw -ErrorAction SilentlyContinue
if ($packageJson) {
if ($packageJson -match "react") {
$techStack += "**Frontend Framework**: React"
}
if ($packageJson -match "vue") {
$techStack += "**Frontend Framework**: Vue"
}
if ($packageJson -match "angular") {
$techStack += "**Frontend Framework**: Angular"
}
if ($packageJson -match "express") {
$techStack += "**Backend Framework**: Express"
}
if ($packageJson -match "fastify") {
$techStack += "**Backend Framework**: Fastify"
}
}
}
# Databases
if ((Test-Path "docker-compose.yml") -or (Test-Path "docker-compose.yaml")) {
$dockerCompose = Get-Content "docker-compose.*" -Raw -ErrorAction SilentlyContinue
if ($dockerCompose) {
if ($dockerCompose -match "postgres") {
$techStack += "**Database**: PostgreSQL"
}
if ($dockerCompose -match "mysql") {
$techStack += "**Database**: MySQL"
}
if ($dockerCompose -match "mongodb") {
$techStack += "**Database**: MongoDB"
}
if ($dockerCompose -match "redis") {
$techStack += "**Cache**: Redis"
}
}
}
# Infrastructure
if (Test-Path "Dockerfile") {
$techStack += "**Containerization**: Docker"
}
if ((Test-Path "kubernetes") -or (Test-Path "k8s")) {
$techStack += "**Orchestration**: Kubernetes"
}
if ((Test-Path "terraform") -or (Get-ChildItem -Filter "*.tf" -ErrorAction SilentlyContinue).Count -gt 0) {
$techStack += "**IaC**: Terraform"
}
if (Test-Path ".github/workflows") {
$techStack += "**CI/CD**: GitHub Actions"
}
if (Test-Path ".gitlab-ci.yml") {
$techStack += "**CI/CD**: GitLab CI"
}
if (Test-Path "Jenkinsfile") {
$techStack += "**CI/CD**: Jenkins"
}
return $techStack -join "`n"
}
# Detect sub-systems from codebase structure
function Get-Subsystems {
Write-Host "Detecting sub-systems from codebase structure..." -ForegroundColor Cyan
$dirs = @()
# 1. Top-level feature directories (src/, app/, services/)
if (Test-Path "src") {
Get-ChildItem -Path "src" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$dirname = $_.Name
# Skip common non-sub-system directories
if (@("utils", "common", "lib", "shared", "core") -notcontains $dirname) {
$dirs += $dirname
}
}
}
if (Test-Path "services") {
Get-ChildItem -Path "services" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$dirs += $_.Name
}
}
if (Test-Path "modules") {
Get-ChildItem -Path "modules" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$dirs += $_.Name
}
}
if (Test-Path "apps") {
Get-ChildItem -Path "apps" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$dirs += $_.Name
}
}
# 2. Check for docker-compose services (microservices indicator)
$composeFile = $null
if (Test-Path "docker-compose.yml") { $composeFile = "docker-compose.yml" }
elseif (Test-Path "docker-compose.yaml") { $composeFile = "docker-compose.yaml" }
if ($composeFile) {
$composeContent = Get-Content $composeFile -Raw
$services = [regex]::Matches($composeContent, '^\s*([a-zA-Z0-9_-]+):\s*$') | ForEach-Object { $_.Groups[1].Value } | Where-Object { @("version", "services", "networks", "volumes") -notcontains $_ }
foreach ($svc in $services) {
$found = $false
foreach ($d in $dirs) {
if ($d.ToLower() -like "*$($svc.ToLower())*" -or $svc.ToLower() -like "*$($d.ToLower())*") {
$found = $true
break
}
}
if (-not $found) {
$dirs += $svc
}
}
}
# 3. Check for Node.js workspaces (monorepo indicator)
if (Test-Path "package.json") {
$packageJson = Get-Content "package.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
if ($packageJson.workspaces) {
$packageJson.workspaces | ForEach-Object {
$dirname = Split-Path $_ -Leaf
if ($dirname -ne "node_modules") {
$dirs += $dirname
}
}
}
}
# 4. Check for Python namespace packages
if (Test-Path "pyproject.toml") {
Get-ChildItem -Path . -Recurse -Filter "__init__.py" -Depth 3 -ErrorAction SilentlyContinue | ForEach-Object {
$dirname = Split-Path $_.DirectoryName -Leaf
if ($dirname -ne "src" -and $dirname -ne ".") {
$dirs += $dirname
}
} | Select-Object -Unique
}
# Remove duplicates
$uniqueDirs = $dirs | Select-Object -Unique
if ($uniqueDirs.Count -gt 0) {
Write-Host "Detected potential sub-systems:" -ForegroundColor Cyan
$count = 0
foreach ($d in $uniqueDirs) {
$count++
Write-Host " - $d" -ForegroundColor Gray
}
Write-Host "Total: $count sub-system(s)" -ForegroundColor Cyan
} else {
Write-Host "No distinct sub-systems detected from directory structure." -ForegroundColor Yellow
}
# Return as JSON if JSON mode
if ($Json) {
$subsystems = @()
foreach ($d in $uniqueDirs) {
$subsystems += @{
id = $d
name = $d
detection_method = "directory"
evidence = "directory: $d/"
}
}
return $subsystems | ConvertTo-Json -Compress
}
}
# Map directory structure
function Get-DirectoryStructure {
Write-Host "Scanning directory structure..." -ForegroundColor Cyan
$structure = @()
if (Test-Path "src") {
$structure += "**Source Code**: src/"
if ((Test-Path "src/api") -or (Test-Path "src/routes")) {
$structure += " - API Layer: src/api/ or src/routes/"
}
if (Test-Path "src/services") {
$structure += " - Business Logic: src/services/"
}
if (Test-Path "src/models") {
$structure += " - Data Models: src/models/"
}
if (Test-Path "src/utils") {
$structure += " - Utilities: src/utils/"
}
}
if ((Test-Path "tests") -or (Test-Path "test")) {
$structure += "**Tests**: tests/ or test/"
}
if (Test-Path "docs") {
$structure += "**Documentation**: docs/"
}
if (Test-Path "scripts") {
$structure += "**Scripts**: scripts/"
}
if ((Test-Path "infra") -or (Test-Path "infrastructure")) {
$structure += "**Infrastructure**: infra/ or infrastructure/"
}
return $structure -join "`n"
}
# Function to scan existing docs for deduplication
function Scan-ExistingDocs {
param([string]$RepoRoot = (Get-RepositoryRoot))
Write-Host "Scanning existing documentation for deduplication..." -ForegroundColor Cyan
$findings = @()
# Check for existing architecture docs
if ((Test-Path "$RepoRoot\docs/adlc/architect/AD.md") -or (Test-Path "$RepoRoot\AD.md")) {
$findings += "EXISTING_AD: $RepoRoot\AD.md"
}
if (Test-Path "$RepoRoot\docs\architecture.md") {
$findings += "EXISTING_ARCHITECTURE: $RepoRoot\docs\architecture.md"
}
# Scan README for tech stack
if (Test-Path "$RepoRoot\README.md") {
$readme = Get-Content "$RepoRoot\README.md" -Raw
if ($readme -match "Tech Stack|Technology|Built with") {
$findings += "TECH_STACK_IN_README: $RepoRoot\README.md"
}
if ($readme -match "PostgreSQL|MySQL|MongoDB|Redis") {
$findings += "DATABASE_IN_README: $RepoRoot\README.md"
}
if ($readme -match "React|Vue|Angular") {
$findings += "FRONTEND_IN_README: $RepoRoot\README.md"
}
}
# Scan AGENTS.md for context
if (Test-Path "$RepoRoot\AGENTS.md") {
$findings += "AGENTS_CONTEXT: $RepoRoot\AGENTS.md"
}
# Scan CONTRIBUTING.md for dev guidelines
if (Test-Path "$RepoRoot\CONTRIBUTING.md") {
$findings += "DEV_GUIDELINES: $RepoRoot\CONTRIBUTING.md"
}
return $findings -join "`n"
}
# Function to parse views parameter
function Parse-Views {
param([string]$ViewsArg)
switch ($ViewsArg.ToLower()) {
"all" { return "context functional information concurrency development deployment operational" }
"full" { return "context functional information concurrency development deployment operational" }
"core" { return "context functional information development deployment" }
"minimal" { return "context functional information development deployment" }
"" { return "context functional information development deployment" }
default {
# Parse comma-separated: "concurrency,operational"
$validViews = @()
$allViews = @("context", "functional", "information", "concurrency", "development", "deployment", "operational")
$requestedViews = $ViewsArg -split ","
foreach ($view in $requestedViews) {
$view = $view.Trim().ToLower()
if ($allViews -contains $view) {
$validViews += $view
}
}
# Always include core views
$coreViews = @("context", "functional", "information", "development", "deployment")
foreach ($coreView in $coreViews) {
if ($validViews -notcontains $coreView) {
$validViews += $coreView
}
}
return $validViews -join " "
}
}
}
# Get architecture diagram format (mermaid or ascii).
# Override via ARCHITECTURE_DIAGRAM_FORMAT env var; defaults to "mermaid".
# Invalid values fall back to "mermaid".
function Get-ArchitectureDiagramFormat {
$format = $env:ARCHITECTURE_DIAGRAM_FORMAT
if ($format -eq 'mermaid' -or $format -eq 'ascii') {
return $format
}
return 'mermaid'
}
# Validate Mermaid diagram syntax (lightweight regex validation)
# Returns $true if valid, $false if invalid
# Args: MermaidCode - Mermaid code string
function Test-MermaidSyntax {
param(
[Parameter(Mandatory=$true)]
[AllowEmptyString()]
[string]$MermaidCode
)
# Check if empty
if ([string]::IsNullOrWhiteSpace($MermaidCode)) {
return $false
}
# Check for basic Mermaid diagram types
if ($MermaidCode -notmatch '^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|journey|gitGraph|mindmap|timeline)') {
return $false
}
# Check for balanced brackets/parentheses (simplified)
$openBrackets = ([regex]::Matches($MermaidCode, '\[')).Count
$closeBrackets = ([regex]::Matches($MermaidCode, '\]')).Count
$openParens = ([regex]::Matches($MermaidCode, '\(')).Count
$closeParens = ([regex]::Matches($MermaidCode, '\)')).Count
if ($openBrackets -ne $closeBrackets -or $openParens -ne $closeParens) {
return $false
}
# Basic syntax passed
return $true
}
# Generate and insert diagrams into architecture.md
function New-ArchitectureDiagrams {
param(
[string]$ArchitectureFile,
[string]$SystemName = "System",
[string]$ViewsList = ""
)
Write-Host "📊 Generating architecture diagrams..." -ForegroundColor Cyan
# Get diagram format from config
$diagramFormat = Get-ArchitectureDiagramFormat
Write-Host " Using diagram format: $diagramFormat"
# Source diagram generators
$scriptDir = Split-Path $PSCommandPath -Parent
if ($diagramFormat -eq 'mermaid') {
. "$scriptDir\mermaid-generator.ps1"
} else {
. "$scriptDir\ascii-generator.ps1"
}
# Determine which views to generate (use env var if ViewsList not provided)
if (-not $ViewsList) {
$ViewsList = $env:ARCHITECTURE_VIEWS
}
if (-not $ViewsList) {
$ViewsList = "core"
}
# Parse views and convert to array
$parsedViews = Parse-Views -ViewsArg $ViewsList
$views = $parsedViews -split ' '
Write-Host " Generating views: $($views -join ', ')"
# Generate each diagram
foreach ($view in $views) {
Write-Host " Generating $view view diagram..."
try {
$diagramCode = if ($diagramFormat -eq 'mermaid') {
New-MermaidDiagram -ViewType $view -SystemName $SystemName
# Validate Mermaid syntax
if (-not (Test-MermaidSyntax -MermaidCode $diagramCode)) {
Write-Host " ⚠️ Mermaid validation failed for $view view, using ASCII fallback" -ForegroundColor Yellow
. "$scriptDir\ascii-generator.ps1"
New-AsciiDiagram -ViewType $view -SystemName $SystemName
}
else {
$diagramCode
}
}
else {
New-AsciiDiagram -ViewType $view -SystemName $SystemName
}
}
catch {
Write-Host " ⚠️ Error generating $view diagram: $_" -ForegroundColor Yellow
}
}
Write-Host "✅ Diagram generation complete" -ForegroundColor Green
}
# Specify action (greenfield - interactive PRD exploration to create ADRs)
function Invoke-Specify {
param($repoRoot, $contextArgs)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
$adrTemplate = Join-Path $repoRoot ".adlc\templates\adr-template.md"
Write-Host "📐 Setting up for interactive ADR creation..." -ForegroundColor Cyan
# Show decomposition status
if ($Decompose) {
Write-Host ""
Write-Host "🔄 Sub-system decomposition: ENABLED" -ForegroundColor Cyan
Write-Host " (AI agent will detect domains from PRD and propose sub-systems)" -ForegroundColor Gray
} else {
Write-Host ""
Write-Host "⚠️ Sub-system decomposition: DISABLED (-NoDecompose flag)" -ForegroundColor Yellow
Write-Host " (AI agent will generate monolithic ADRs)" -ForegroundColor Gray
}
# Ensure memory directory exists
$memoryDir = Join-Path $repoRoot ".adlc\memory"
if (-not (Test-Path $memoryDir)) {
New-Item -ItemType Directory -Path $memoryDir -Force | Out-Null
}
# Initialize ADR file from template if it doesn't exist
if (-not (Test-Path $adrDir)) {
if (Test-Path $adrTemplate) {
Write-Host "Creating ADR file from template..." -ForegroundColor Cyan
Copy-Item $adrTemplate $adrDir
Write-Host "✅ Created: $adrDir" -ForegroundColor Green
} else {
# Create minimal ADR file
$minimalAdr = @"
# Architecture Decision Records
## ADR Index
| ID | Sub-System | Decision | Status | Date | Owner |
|----|------------|----------|--------|------|-------|
---
"@
Set-Content -Path $adrDir -Value $minimalAdr
Write-Host "✅ Created minimal ADR file: $adrDir" -ForegroundColor Green
}
} else {
Write-Host "✅ ADR file already exists: $adrDir" -ForegroundColor Green
}
Write-Host ""
Write-Host "Ready for interactive PRD exploration."
Write-Host "The AI agent will:"
if ($Decompose) {
Write-Host " 0. (Phase 0) Detect domains in PRD and propose sub-systems" -ForegroundColor Gray
Write-Host " → Ask user to confirm sub-system breakdown" -ForegroundColor Gray
}
Write-Host " 1. Analyze your PRD/requirements input"
Write-Host " 2. Ask clarifying questions about architecture"
Write-Host " 3. Create ADRs for each key decision"
Write-Host " 4. Save decisions to .adlc/drafts/adr/ADR-{NNN}.md (Proposed status)"
Write-Host " (ADRs will be moved to docs/adlc/memory after /architect-implement)"
if ($Decompose) {
Write-Host " 5. Organize ADRs by sub-system"
}
Write-Host ""
Write-Host "After completion, run '/architect-implement' to generate full AD.md"
# Ledger slot allocation: hand callers the next free slot (leading digits
# of the filename stem, max across drafts AND published memory -- see
# Get-NextAdrNumber). Leaving "the highest existing number" to the agent
# reissued taken slots.
$nextAdr = Get-NextAdrNumber -Root $repoRoot
Write-Host ""
Write-Host "Next free ADR slot: ADR-$nextAdr"
if ($Json) {
@{status="success"; action="specify"; adr_dir=$adrDir; context=($contextArgs -join " "); decomposition=$Decompose; next_adr=$nextAdr} | ConvertTo-Json
}
}
# Clarify action (refine existing ADRs)
function Invoke-Clarify {
param($repoRoot, $contextArgs)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
if (-not (Test-Path $adrDir)) {
Write-Error "ADR file does not exist: $adrDir`nRun '/architect-specify' or '/architect-init' first"
exit 1
}
Write-Host "🔍 Loading existing ADRs for clarification..." -ForegroundColor Cyan
# Count existing ADRs (individual ADR-{NNN}.md files in the directory)
$adrCount = (Get-ChildItem -Path $adrDir -Filter "ADR-*.md" -File -ErrorAction SilentlyContinue).Count
Write-Host "Found $adrCount ADR(s) in $adrDir"
Write-Host ""
Write-Host "Ready for ADR refinement."
Write-Host "The AI agent will:"
Write-Host " 1. Review existing ADRs"
Write-Host " 2. Ask targeted clarification questions"
Write-Host " 3. Update ADRs based on your responses"
Write-Host " 4. Flag any inconsistencies or gaps"
if ($Json) {
@{status="success"; action="clarify"; adr_dir=$adrDir; adr_count=$adrCount; context=($contextArgs -join " ")} | ConvertTo-Json
}
}
# Implement action (generate full AD.md from ADRs)
function Invoke-Implement {
param($repoRoot, $contextArgs)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
$adFile = Join-Path $repoRoot "docs/adlc/architect/AD.md"
$adTemplate = Join-Path $repoRoot ".adlc\templates\AD-template.md"
if (-not (Test-Path $adrDir)) {
Write-Error "ADR file does not exist: $adrDir`nRun '/architect-specify' or '/architect-init' first"
exit 1
}
Write-Host "📐 Setting up for Architecture Description generation..." -ForegroundColor Cyan
# Initialize AD.md from template if it doesn't exist
if (-not (Test-Path $adFile)) {
if (Test-Path $adTemplate) {
Write-Host "Creating AD.md from template..." -ForegroundColor Cyan
New-Item -ItemType Directory -Force -Path (Split-Path $adFile -Parent) | Out-Null
Copy-Item $adTemplate $adFile
Write-Host "✅ Created: $adFile" -ForegroundColor Green
} else {
Write-Host "⚠️ AD template not found: $adTemplate" -ForegroundColor Yellow
Write-Host "The AI agent will create AD.md from scratch"
}
} else {
Write-Host "✅ AD.md already exists, will be updated: $adFile" -ForegroundColor Green
}
# Count ADRs for context (individual ADR-{NNN}.md files in the directory)
$adrCount = (Get-ChildItem -Path $adrDir -Filter "ADR-*.md" -File -ErrorAction SilentlyContinue).Count
Write-Host ""
Write-Host "Ready for Architecture Description generation."
Write-Host "The AI agent will:"
Write-Host " 1. Read all $adrCount ADR(s) from $adrDir"
Write-Host " 2. Generate 7 Rozanski & Woods viewpoints"
Write-Host " 3. Apply Security and Performance perspectives"
Write-Host " 4. Create Mermaid diagrams for each view"
Write-Host " 5. Write complete AD.md to docs/adlc/architect/"
Write-Host " 6. Move Accepted ADRs to canonical location"
Write-Host " 7. Clean up drafts if all ADRs are Accepted"
$nextDelta = Get-NextAdDeltaVersion -File $adFile
if ($Json) {
@{status="success"; action="implement"; adr_dir=$adrDir; ad_file=$adFile; adr_count=$adrCount; context=($contextArgs -join " "); next_ad_delta_version=$nextDelta} | ConvertTo-Json
}
}
# Initialize action (brownfield - reverse-engineer from codebase, ADRs only)
function Invoke-Init {
param($repoRoot, $contextArgs)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
$adrTemplate = Join-Path $repoRoot ".adlc\templates\adr-template.md"
Write-Host "🔍 Initializing brownfield architecture discovery..." -ForegroundColor Cyan
# Ensure memory directory exists
$memoryDir = Join-Path $repoRoot ".adlc\memory"
if (-not (Test-Path $memoryDir)) {
New-Item -ItemType Directory -Path $memoryDir -Force | Out-Null
}
# Scan existing docs for deduplication
Write-Host ""
$existingDocs = Scan-ExistingDocs -RepoRoot $repoRoot
if ($existingDocs) {
Write-Host "📋 Found existing documentation:" -ForegroundColor Yellow
$existingDocs -split "`n" | ForEach-Object { Write-Host " - $_" }
Write-Host ""
}
# Detect tech stack for context
Write-Host "🔍 Scanning codebase..." -ForegroundColor Cyan
$techStack = Get-TechStack
$dirStructure = Get-DirectoryStructure
# Phase 0: Sub-system detection (if decomposition enabled)
$subsystemsJson = ""
$decomposeStatus = "disabled"
if ($Decompose) {
Write-Host ""
Write-Host "🔄 Phase 0: Sub-System Detection" -ForegroundColor Cyan
$subsystemsJson = Get-Subsystems
$decomposeStatus = "enabled"
if ($subsystemsJson -and $subsystemsJson -ne "[]") {
Write-Host ""
Write-Host "📦 Sub-systems will be used to organize ADRs" -ForegroundColor Cyan
Write-Host " (AI agent will confirm with user before proceeding)" -ForegroundColor Gray
}
} else {
Write-Host ""
Write-Host "⚠️ Sub-system decomposition disabled (-NoDecompose flag)" -ForegroundColor Yellow
}
# Initialize ADR file from template if it doesn't exist
if (-not (Test-Path $adrDir)) {
if (Test-Path $adrTemplate) {
Write-Host "Creating ADR file from template..." -ForegroundColor Cyan
Copy-Item $adrTemplate $adrDir
Write-Host "✅ Created: $adrDir" -ForegroundColor Green
} else {
# Create minimal ADR file
$minimalAdr = @"
# Architecture Decision Records
## ADR Index
| ID | Sub-System | Decision | Status | Date | Owner |
|----|------------|----------|--------|------|-------|
---
"@
Set-Content -Path $adrDir -Value $minimalAdr
Write-Host "✅ Created minimal ADR file: $adrDir" -ForegroundColor Green
}
} else {
Write-Host "✅ ADR file already exists: $adrDir" -ForegroundColor Green
}
Write-Host ""
Write-Host "📊 Codebase Analysis Summary:" -ForegroundColor Cyan
Write-Host $techStack
Write-Host ""
Write-Host $dirStructure
Write-Host ""
Write-Host "Ready for brownfield architecture discovery."
Write-Host "The AI agent will:"
if ($Decompose) {
Write-Host " 0. (Phase 0) Propose sub-systems from code structure" -ForegroundColor Gray
Write-Host " → Ask user to confirm sub-system breakdown" -ForegroundColor Gray
}
Write-Host " 1. Analyze codebase structure and patterns"
Write-Host " 2. Infer architectural decisions from code"
Write-Host " 3. Create ADRs marked as 'Discovered (Inferred)'"
if ($Decompose) {
Write-Host " 4. Organize ADRs by sub-system"
}
Write-Host " 5. Auto-trigger /architect-clarify to validate findings"
Write-Host ""
Write-Host "NOTE: AD.md will NOT be created until ADRs are validated." -ForegroundColor Yellow
Write-Host " After clarification, run /architect-implement to generate AD.md"
if ($Json) {
@{
status="success"
action="init"
adr_dir=$adrDir
tech_stack=$techStack
existing_docs=$existingDocs
source="brownfield"
decomposition=$decomposeStatus
subsystems=$subsystemsJson
} | ConvertTo-Json
}
}
# Map action (brownfield)
function Invoke-Map {
param($repoRoot)
Write-Host "🔍 Mapping existing codebase to architecture..." -ForegroundColor Cyan
# Scan existing docs for deduplication
Write-Host ""
$existingDocs = Scan-ExistingDocs -RepoRoot $repoRoot
if ($existingDocs) {
Write-Host "Existing Documentation Found:" -ForegroundColor Yellow
Write-Host $existingDocs
Write-Host ""
}
# Detect tech stack
Write-Host ""
Write-Host "Tech Stack Detected:" -ForegroundColor Yellow
$techStack = Get-TechStack
Write-Host $techStack
# Map directory structure
Write-Host ""
Write-Host "Code Organization:" -ForegroundColor Yellow
$dirStructure = Get-DirectoryStructure
Write-Host $dirStructure
# Output structured data for AI agent to populate AD.md Section C
if ($Json) {
@{
status="success"
action="map"
tech_stack=$techStack
directory_structure=$dirStructure
existing_docs=$existingDocs
} | ConvertTo-Json
} else {
Write-Host ""
Write-Host "📋 Mapping complete. Use this information to populate AD.md:"
Write-Host " - Section C (Tech Stack Summary): Use detected technologies above"
Write-Host " - Development View: Use directory structure above"
Write-Host " - Deployment View: Check docker-compose.yml, k8s configs, terraform"
Write-Host " - Functional View: Use API endpoints detected"
Write-Host " - Information View: Check database schemas, ORM models"
Write-Host " - ⚠️ Reference existing docs instead of duplicating"
}
}
# Update action
function Invoke-Update {
param($repoRoot, $architectureFile)
if (-not (Test-Path $architectureFile)) {
Write-Error "Architecture does not exist: $architectureFile`nRun '/architect-specify' or '/architect-init' first"
exit 1
}
Write-Host "🔄 Updating architecture based on recent changes..." -ForegroundColor Cyan
# Check for recent commits
if (Get-Command git -ErrorAction SilentlyContinue) {
Write-Host ""
Write-Host "Recent changes:" -ForegroundColor Yellow
git log --oneline --since="7 days ago" 2>$null | Select-Object -First 10
}
# Detect current tech stack
Write-Host ""
Write-Host "Current Tech Stack:" -ForegroundColor Yellow
Get-TechStack
# Regenerate diagrams with current format
New-ArchitectureDiagrams -ArchitectureFile $architectureFile -SystemName "System"
Write-Host ""
Write-Host "✅ Update analysis complete" -ForegroundColor Green
Write-Host "Review the architecture document and update affected sections:"
Write-Host " - New tables/models? → Update Information View"
Write-Host " - New services/components? → Update Functional View + Deployment View"
Write-Host " - New queues/async? → Update Concurrency View"
Write-Host " - New dependencies? → Update Development View"
Write-Host " - Add ADR if significant decision was made"
if ($Json) {
@{status="success"; action="update"; file=$architectureFile} | ConvertTo-Json
}
}
# Review action
function Invoke-Review {
param($repoRoot, $architectureFile)
if (-not (Test-Path $architectureFile)) {
Write-Error "Architecture does not exist: $architectureFile`nRun '/architect-specify' or '/architect-init' first"
exit 1
}
Write-Host "🔍 Reviewing architecture..." -ForegroundColor Cyan
$issues = @()
$content = Get-Content $architectureFile -Raw
# Check for required sections
if ($content -notmatch "## 1\. Introduction") {
$issues += "Missing: Introduction section"
}
if ($content -notmatch "## 2\. Stakeholders & Concerns") {
$issues += "Missing: Stakeholders section"
}
if ($content -notmatch "## 3\. Architectural Views") {
$issues += "Missing: Architectural Views section"
}
if ($content -notmatch "### 3\.1 Context View") {
$issues += "Missing: Context View"
}
if ($content -notmatch "### 3\.2 Functional View") {
$issues += "Missing: Functional View"
}
if ($content -notmatch "## 4\. Architectural Perspectives") {
$issues += "Missing: Perspectives section"
}
if ($content -notmatch "## 5\. Global Constraints & Principles") {
$issues += "Missing: Global Constraints section"
}
# Check for placeholders
if ($content -match "\[SYSTEM_NAME\]") {
$issues += "Placeholder: System name not filled in"
}
if ($content -match "\[STAKEHOLDER_") {
$issues += "Placeholder: Stakeholders not filled in"
}
# Report results
if ($issues.Count -eq 0) {
Write-Host "✅ Architecture review passed - no major issues found" -ForegroundColor Green
} else {
Write-Host "⚠️ Architecture review found issues:" -ForegroundColor Yellow
foreach ($issue in $issues) {
Write-Host " - $issue"
}
}
# Check constitution alignment (dual-read: docs/adlc/memory first, legacy fallback)
$constitutionFile = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "constitution.md"
if (Test-Path $constitutionFile) {
Write-Host ""
Write-Host "📜 Checking constitution alignment..." -ForegroundColor Cyan
Write-Host "✅ Constitution file found: $constitutionFile" -ForegroundColor Green
Write-Host "Manually verify that architecture adheres to constitutional principles"
}
if ($Json) {
if ($issues.Count -eq 0) {
@{status="success"; action="review"; issues=@()} | ConvertTo-Json
} else {
@{status="warning"; action="review"; issues=$issues} | ConvertTo-Json
}
}
}
# Analyze action (validate architecture consistency)
function Invoke-Analyze {
param(
[string]$repoRoot,
[string[]]$contextArgs
)
Write-Host "🔍 Architecture Analysis Mode" -ForegroundColor Cyan
Write-Host ""
# dual-read: compiled AD at docs/adlc/architect/AD.md, legacy repo-root AD.md fallback.
$adFile = Join-Path $repoRoot "docs/adlc/architect/AD.md"
if (-not (Test-Path $adFile)) { $adFile = Join-Path $repoRoot "AD.md" }
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
$constitutionFile = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "constitution.md"
$adExists = Test-Path $adFile
$adrExists = Test-Path $adrDir
$constitutionExists = Test-Path $constitutionFile
if ($adExists) {
Write-Host "📄 AD.md found: $adFile" -ForegroundColor Green
} else {
Write-Host "⚠️ AD.md not found at $adFile" -ForegroundColor Yellow
}
if ($adrExists) {
$adrCount = (Get-ChildItem -Path $adrDir -Filter "ADR-*.md" -File -ErrorAction SilentlyContinue).Count
Write-Host "📋 ADR file found: $adrDir ($adrCount ADRs)" -ForegroundColor Green
} else {
Write-Host "⚠️ ADR file not found at $adrDir" -ForegroundColor Yellow
}
if ($constitutionExists) {
Write-Host "📜 Constitution found: $constitutionFile" -ForegroundColor Green
}
$featureAds = @()
$featureAdrs = @()
$specsDir = Join-Path $repoRoot "specs"
if (Test-Path $specsDir) {
$featureAds = Get-ChildItem -Path $specsDir -Recurse -Filter "AD.md" -File -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
$featureAdrs = Get-ChildItem -Path $specsDir -Recurse -Filter "adr.md" -File -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
if ($featureAds.Count -gt 0) {
Write-Host "📁 Feature ADs found: $($featureAds.Count)" -ForegroundColor Cyan
}
if ($featureAdrs.Count -gt 0) {
Write-Host "📁 Feature ADRs found: $($featureAdrs.Count)" -ForegroundColor Cyan
}
}
Write-Host ""
Write-Host "Ready for architecture consistency analysis."
Write-Host "The AI agent will:"
Write-Host " 1. Load all architecture artifacts"
Write-Host " 2. Execute detection passes A-G"
Write-Host " 3. Assign severity levels to findings"
Write-Host " 4. Generate structured analysis report"
Write-Host " 5. Suggest remediation actions"
if ($Json) {
@{
status = "success"
action = "analyze"
ad_file = $adFile
ad_exists = $adExists
adr_dir = $adrDir
adr_exists = $adrExists
constitution_file = $constitutionFile
constitution_exists = $constitutionExists
feature_ads = $featureAds
feature_adrs = $featureAdrs
context = ($contextArgs -join " ")
} | ConvertTo-Json
}
}
# Validate action (READ-ONLY architecture validation for plan alignment)
function Invoke-Validate {
param($repoRoot, $contextArgs)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
Write-Host "🔍 Architecture Validation Mode (READ-ONLY)" -ForegroundColor Cyan
Write-Host ""
# Check if architecture exists
if (-not (Test-Path $adrDir)) {
Write-Host "⏭️ Architecture not found: $adrDir" -ForegroundColor Yellow
Write-Host " Skipping validation gracefully" -ForegroundColor Gray
if ($Json) {
@{status="skipped"; action="validate"; reason="architecture_not_found"} | ConvertTo-Json
}
exit 0
}
$adrCount = (Get-ChildItem -Path $adrDir -Filter "ADR-*.md" -File -ErrorAction SilentlyContinue).Count
Write-Host "📋 ADR file found: $adrDir" -ForegroundColor Green
Write-Host " Found $adrCount ADR(s)" -ForegroundColor Cyan
Write-Host ""
Write-Host "Ready for READ-ONLY architecture validation."
Write-Host "The AI agent will:"
Write-Host " 1. Load architecture from ADRs and AD.md"
Write-Host " 2. Validate plan alignment with architecture"
Write-Host " 3. Identify blocking/high-severity issues"
Write-Host " 4. Report findings (READ-ONLY, no modifications)"
if ($Json) {
@{status="success"; action="validate"; adr_dir=$adrDir; adr_count=$adrCount; context=($contextArgs -join " ")} | ConvertTo-Json
}
}
# Plan DAG action (Phase 1 of implement - generate execution plan)
function Invoke-PlanDag {
param($repoRoot, $contextArgs)
$adrDir = Join-Path $repoRoot ".adlc\drafts\adr"
$stateFile = Join-Path $repoRoot ".adlc\architect\state.json"
$viewsDir = Join-Path $repoRoot "docs/adlc/architect/views"
Write-Host "📐 DAG Planning Phase" -ForegroundColor Cyan
Write-Host ""
# Check if ADR file exists
if (-not (Test-Path $adrDir)) {
Write-Error "ADR drafts file does not exist: $adrDir`nRun '/architect-specify' or '/architect-init' first"
exit 1
}
# Ensure directories exist
$architectDir = Join-Path $repoRoot ".adlc\architect"
if (-not (Test-Path $architectDir)) {
New-Item -ItemType Directory -Path $architectDir -Force | Out-Null
}
if (-not (Test-Path $viewsDir)) {
New-Item -ItemType Directory -Path $viewsDir -Force | Out-Null
}
# Count ADRs (individual ADR-{NNN}.md files) and extract sub-systems from the index
$adrCount = (Get-ChildItem -Path $adrDir -Filter "ADR-*.md" -File -ErrorAction SilentlyContinue).Count
# Extract unique sub-systems from the generated adr.md index table (written by setup-architect.sh)
$subsystems = @()
$indexFile = Join-Path $adrDir "adr.md"
if (Test-Path $indexFile) {
$lines = Get-Content $indexFile
foreach ($line in $lines) {
# Parse ADR index table rows: | ADR-XXX | SubSystem | ...
# ID class is alphanumeric+dash: suffixed variants (e.g. ADR-<id>-amendment-2) must match, not just bare numerics.
if ($line -match '^\|\s*ADR-[0-9A-Za-z-]+\s*\|\s*([^|]+)\s*\|') {
$subsystem = $Matches[1].Trim()
if ($subsystem -and $subsystem -ne "Sub-System" -and $subsystems -notcontains $subsystem) {
$subsystems += $subsystem
}
}
}
}
# Default to "System" if no sub-systems found
if ($subsystems.Count -eq 0) {
$subsystems = @("System")
}
Write-Host "📋 ADR file found: $adrDir" -ForegroundColor Green
Write-Host " Found $adrCount ADR(s)" -ForegroundColor Cyan
Write-Host " Sub-systems detected: $($subsystems -join ', ')" -ForegroundColor Cyan
Write-Host ""
Write-Host "Ready for DAG planning."
Write-Host "The AI agent will:"
Write-Host " 1. Analyze ADRs by sub-system"
Write-Host " 2. Generate customized DAG per sub-system"
Write-Host " 3. Present execution plan for user approval"
Write-Host " 4. Save approved plan to state.json"
if ($Json) {
$subsystemsJson = $subsystems | ForEach-Object {
@{
id = ($_ -replace '\s+', '-').ToLower()
name = $_
}
}
@{
status = "success"
action = "plan-dag"
adr_dir = $adrDir
state_file = $stateFile
views_dir = $viewsDir
adr_count = $adrCount
subsystems = $subsystemsJson
context = ($contextArgs -join " ")
} | ConvertTo-Json -Depth 3
}
}
# Execute DAG action (Phase 2 of implement - generate views based on state)
function Invoke-ExecuteDag {
param($repoRoot, $contextArgs)
$stateFile = Join-Path $repoRoot ".adlc\architect\state.json"
$viewsDir = Join-Path $repoRoot "docs/adlc/architect/views"
Write-Host "🔧 DAG Execution Phase" -ForegroundColor Cyan
Write-Host ""
# Check if state file exists
if (-not (Test-Path $stateFile)) {
Write-Error "No execution plan found: $stateFile`nRun '/architect-implement' first to generate and approve a DAG plan"
exit 1
}
# Ensure views directory exists
if (-not (Test-Path $viewsDir)) {
New-Item -ItemType Directory -Path $viewsDir -Force | Out-Null
}
Write-Host "📄 State file found: $stateFile" -ForegroundColor Green
Write-Host "📁 Views directory: $viewsDir" -ForegroundColor Cyan
Write-Host ""
Write-Host "Ready for DAG execution."
Write-Host "The AI agent will:"
Write-Host " 1. Read execution plan from state.json"
Write-Host " 2. Identify next view(s) to generate"
Write-Host " 3. Generate view with dependency context"
Write-Host " 4. Write to docs/adlc/architect/views/{subsystem}/{view}.md"
Write-Host " 5. Update progress in state.json"
if ($Json) {
$stateContent = Get-Content $stateFile -Raw | ConvertFrom-Json
@{
status = "success"
action = "execute-dag"
state_file = $stateFile
views_dir = $viewsDir
state = $stateContent
} | ConvertTo-Json -Depth 10
}
}
# Summarize action (Phase 3 of implement - aggregate views into AD.md)
function Invoke-Summarize {
param($repoRoot, $contextArgs)
$stateFile = Join-Path $repoRoot ".adlc\architect\state.json"
$viewsDir = Join-Path $repoRoot "docs/adlc/architect/views"
$adFile = Join-Path $repoRoot "docs/adlc/architect/AD.md"
$adrDir = Join-Path $repoRoot ".adlc\drafts\adr"
Write-Host "📝 Summarization Phase" -ForegroundColor Cyan
Write-Host ""
# Check if views directory exists and has content
if (-not (Test-Path $viewsDir)) {
Write-Error "Views directory not found: $viewsDir`nRun '/architect-implement' to generate views first"
exit 1
}
# Count view files
$viewFiles = Get-ChildItem -Path $viewsDir -Filter "*.md" -Recurse -File
$viewCount = $viewFiles.Count
if ($viewCount -eq 0) {
Write-Error "No view files found in $viewsDir`nRun '/architect-implement' to generate views first"
exit 1
}
Write-Host "📁 Views directory: $viewsDir" -ForegroundColor Cyan
Write-Host " Found $viewCount view file(s):" -ForegroundColor Cyan
foreach ($file in $viewFiles) {
$relativePath = $file.FullName.Substring($viewsDir.Length + 1)
Write-Host " - $relativePath" -ForegroundColor Gray
}
Write-Host ""
Write-Host "Ready for summarization."
Write-Host "The AI agent will:"
Write-Host " 1. Read all view files from docs/adlc/architect/views/"
Write-Host " 2. Detect cross-subsystem conflicts"
Write-Host " 3. Resolve conflicts using ADRs as source of truth"
Write-Host " 4. Aggregate into unified AD.md"
Write-Host " 5. Apply Security and Performance perspectives"
Write-Host " 6. Move Accepted ADRs to canonical location"
if ($Json) {
$viewsJson = $viewFiles | ForEach-Object {
@{
path = $_.FullName
relative = $_.FullName.Substring($viewsDir.Length + 1)
}
}
@{
status = "success"
action = "summarize"
state_file = $stateFile
views_dir = $viewsDir
ad_file = $adFile
adr_dir = $adrDir
view_count = $viewCount
views = $viewsJson
} | ConvertTo-Json -Depth 3
}
}
# Main execution -- runs when the file is executed. Dot-sourcing this file
# (the documented way to reach Get-NextAdrNumber / Get-NextAdDeltaVersion)
# must define helpers without running the action dispatch below.
if ($MyInvocation.InvocationName -ne '.') {
try {
$repoRoot = Get-RepositoryRoot
Seed-Templates -RepoRoot $repoRoot
# Ensure memory directory exists
$memoryDir = Join-Path $repoRoot ".adlc\memory"
if (-not (Test-Path $memoryDir)) {
New-Item -ItemType Directory -Path $memoryDir -Force | Out-Null
}
# Architecture files (new structure: AD.md at root, ADRs in memory/)
$adFile = Join-Path $repoRoot "docs/adlc/architect/AD.md"
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$adrDir = Get-AdlcMemoryPath -RepoRoot $repoRoot -RelPath "adr"
$templateFile = Join-Path $repoRoot ".adlc\templates\architecture-template.md"
$adTemplateFile = Join-Path $repoRoot ".adlc\templates\AD-template.md"
# Export for use in functions
$env:ARCHITECTURE_VIEWS = $Views
$env:ADR_HEURISTIC = $AdrHeuristic
# Default action if not specified
if (-not $Action) {
if (Test-Path $adFile) {
$Action = "update"
} else {
$Action = "init"
}
}
# Execute action
switch ($Action) {
'specify' {
Invoke-Specify -repoRoot $repoRoot -contextArgs $Context
}
'clarify' {
Invoke-Clarify -repoRoot $repoRoot -contextArgs $Context
}
'init' {
Invoke-Init -repoRoot $repoRoot -contextArgs $Context
}
'map' {
Invoke-Map -repoRoot $repoRoot
}
'implement' {
Invoke-Implement -repoRoot $repoRoot -contextArgs $Context
}
'analyze' {
Invoke-Analyze -repoRoot $repoRoot -contextArgs $Context
}
'validate' {
Invoke-Validate -repoRoot $repoRoot -contextArgs $Context
}
'plan-dag' {
Invoke-PlanDag -repoRoot $repoRoot -contextArgs $Context
}
'execute-dag' {
Invoke-ExecuteDag -repoRoot $repoRoot -contextArgs $Context
}
'summarize' {
Invoke-Summarize -repoRoot $repoRoot -contextArgs $Context
}
'update' {
Invoke-Update -repoRoot $repoRoot -architectureFile $adFile
}
'review' {
Invoke-Review -repoRoot $repoRoot -architectureFile $adFile
}
default {
Write-Error "Unknown action: $Action`nUse -Help for usage information"
exit 1
}
}
} catch {
Write-Error $_.Exception.Message
exit 1
}
} # end execution-only guard (see the sourcing contract above)
# Get-NextAdDeltaVersion AD_FILE - the next `## vN.N.N Delta:` heading version.
#
# Deltas bump the MINOR and reset the patch: v13.18.0 -> v13.19.0. A patch bump
# means amending the delta already there, which is a deliberate act, not the
# next free slot -- so this never emits one.
function Get-NextAdDeltaVersion {
param([string]$File)
$major = 0; $minor = 0; $patch = 0
if (Test-Path $File) {
$best = $null
foreach ($m in [regex]::Matches((Get-Content $File -Raw),
'(?m)^## v(\d+)\.(\d+)\.(\d+) Delta')) {
$v = @([int]$m.Groups[1].Value, [int]$m.Groups[2].Value, [int]$m.Groups[3].Value)
if ($null -eq $best -or ($v[0] -gt $best[0]) -or
($v[0] -eq $best[0] -and $v[1] -gt $best[1]) -or
($v[0] -eq $best[0] -and $v[1] -eq $best[1] -and $v[2] -gt $best[2])) {
$best = $v
}
}
if ($null -ne $best) { $major = $best[0]; $minor = $best[1]; $patch = $best[2] }
}
return "v$major.$($minor + 1).0"
}
SKILL.md
---
name: architect-clarify
description: Use when ADRs need review, gaps need filling, or ADR status must be approved as Accepted before architecture generation.
disable-model-invocation: true
---
# architect-clarify
## What this skill does
Identify underspecified areas in existing ADRs and refine them through targeted clarification questions. Ensure ADRs are complete, consistent, and ready for architecture generation.
You act as an **Architecture Reviewer** ensuring ADR quality by:
- **Validating** ADR completeness against MADR standards
- **Identifying** gaps in consequences or alternatives
- **Detecting** conflicts between ADRs or with constitution
- **Refining** decisions through targeted clarification
## When to use
Use this skill:
- **After `/architect-init`**: Refine initial ADRs before architecture generation
- **ADR Review**: Validate ADRs before major milestones
- **New Team Member**: Ensure ADRs make sense to fresh eyes
- **Conflict Resolution**: Resolve identified inconsistencies
Do **not** use this skill when:
- **No ADRs exist**: Use `/architect-init` first to create ADRs
- **Brownfield projects**: Use `/architect-init` to reverse-engineer ADRs from code
## Process
### User Input
```text
$ARGUMENTS
```
You **MUST** consider the user input before proceeding (if not empty).
**Examples of User Input**:
- `"Focus on data architecture decisions - we're reconsidering database choice"`
- `"Security ADRs need more detail for compliance review"`
- `"ADR-003 consequences seem incomplete"`
- Empty input: Review all ADRs for completeness
### Goal
Identify underspecified areas in existing ADRs and refine them through targeted clarification questions. Ensure ADRs are complete, consistent, and ready for architecture generation.
### ADR Quality Checklist
Each ADR should have:
- [ ] Clear context explaining the problem/opportunity
- [ ] Explicit decision statement
- [ ] Positive AND negative consequences
- [ ] **Common Alternatives** documented (neutral trade-offs, not "Rejected because")
- [ ] Risks identified with mitigation strategies
- [ ] Status is accurate (Proposed/Accepted/Deprecated/Superseded/**Discovered**)
- [ ] No conflicts with other ADRs
- [ ] Alignment with constitution principles
- [ ] **No fabricated rejection rationale** for reverse-engineered ADRs
### Outline
1. **Load Current State**: Parse ADRs from `{REPO_ROOT}/.adlc/drafts/adr/` (individual file format) and `{REPO_ROOT}/docs/adlc/memory/constitution.md` (legacy `{REPO_ROOT}/.adlc/memory/constitution.md` fallback — dual-read)
**IMPORTANT - Path Resolution (REPO_ROOT + scope)**:
- The setup script outputs `REPO_ROOT` - use this to determine the correct paths
- REPO_ROOT is found by searching upward from current directory for `.adlc` directory (when running helpers from zsh, source the `scripts/bash/` file under `bash -c` or `emulate -L sh`: the scripts rely on `BASH_SOURCE[0]` for path resolution and will not locate helpers under zsh)
- NEVER use relative paths like `.adlc/drafts/adr.md` - always use `{REPO_ROOT}/.adlc/drafts/adr/ADR-{NNN}.md`
- The setup script reads individual ADR files from the `adr/` directory
- When running from a subdirectory (e.g., a subproject directory), `.adlc` may be in the parent directory
- `move_adr`/`get_adr_by_id` accept bare IDs (`8`), zero-padded IDs (`008`), and full suffixed stems (`008-amendment-2`): the `PDR-`/`ADR-` prefix is optional and callers must never pre-strip it. Bare `8` and `008` resolve to the same file.
2. **Analyze ADRs**: Check each ADR against quality checklist
3. **Identify Gaps**: List areas needing clarification
4. **Interactive Refinement**: Ask targeted questions to fill gaps
5. **Update ADRs**: Write refined ADRs back to file
### Execution Steps
#### Phase 1: Context Loading
**Objective**: Establish current ADR state
1. **Run Prerequisites Script**:
- Execute `scripts/bash/setup-architect.sh <action>` from repo root — never bare: the script needs an explicit action (`specify` for greenfield, `init` for brownfield, `clarify` to refine, `implement` to compile). With no action and no AD.md it defaults to `init`, not `specify`.
- Parse JSON for paths to memory files
- Handle errors gracefully if files don't exist
2. **Load ADR File**:
- Read `{REPO_ROOT}/.adlc/drafts/adr/adr.md` for the ADR list
- Read individual ADRs from `{REPO_ROOT}/.adlc/drafts/adr/ADR-{NNN}.md`
- Count total ADRs and identify status distribution
3. **Load Constitution**:
- Read `{REPO_ROOT}/docs/adlc/memory/constitution.md` (legacy `{REPO_ROOT}/.adlc/memory/constitution.md` fallback) if either exists
- Extract principles for alignment checking
- Note governance constraints
4. **User Focus**:
- If user specified specific ADRs or areas, narrow scope
- Otherwise, review all ADRs
#### Step 0: Detect Lightweight Draft Format
Check if the draft being reviewed uses the lightweight draft template (indicated
by presence of `type`, `evidence`, `source`, `revisit-when` fields in frontmatter
and `## Rejected Alternatives` / `## Reason` body sections without the full
formal template sections).
If lightweight:
1. Read the draft's captured fields (Context, Decision, Rejected Alternatives, Reason)
2. Transform to full formal template:
- ADR: full MADR format with Decision Drivers, Considered Options, Pros/Cons, Constitution Alignment, Related ADRs
- PDR: full PDR format with Market Forces, Consequences, Alternatives Considered, Links
- ChDR: full ChDR format with Issue Links, Commits, Consequences, Evidence
- CDR: full CDR format with Context Type, Target Module, Descriptor, Evidence
- EVAL: full eval format with Error Analysis, Pass/Fail Examples, Implementation Notes
3. Enrich from session context (add details the lightweight draft may have omitted)
4. Present the enriched draft for review
If already full format, proceed with normal review.
#### Phase 2: ADR Analysis
**Objective**: Identify quality gaps in each ADR
##### Quality Dimensions to Check
1. **Context Completeness**:
- Is the problem clearly stated?
- Are the forces/drivers documented?
- Is the decision scope clear?
2. **Decision Clarity**:
- Is the decision actionable?
- Are implementation implications clear?
- Can this decision be validated/tested?
3. **Consequence Coverage**:
- Are positive outcomes documented?
- Are negative trade-offs acknowledged?
- Are risks identified with mitigations?
4. **Alternatives Documentation**:
- Are at least 2 alternatives listed?
- Is rejection reasoning clear for each?
- Were reasonable options considered?
5. **Cross-ADR Consistency**:
- Do decisions conflict with other ADRs?
- Are dependencies between ADRs documented?
- Is terminology consistent?
6. **Constitution Alignment**:
- Does decision comply with MUST principles?
- Are SHOULD principles addressed?
- Are violations explicitly justified?
#### Phase 2.5: Risks & Gaps Analysis (Cross-Cutting)
**Objective**: Identify operational gaps, technical debt, SPOFs, and security concerns NOT documented across all views
**Analysis Dimensions**:
| Dimension | Views to Check | What to Look For |
|-----------|---------------|------------------|
| **Operational Gaps** | Operational (3.7), Deployment (3.6) | Missing monitoring, undefined on-call, no runbooks, unclear rollback |
| **Technical Debt** | Development (3.5), Information (3.3) | Deprecated dependencies, no tests, legacy code, schema debt |
| **Single Points of Failure** | Deployment (3.6), Concurrency (3.4) | Single DB instance, no redundancy, critical path bottlenecks |
| **Security Concerns** | All views | Unencrypted data, no auth, exposed secrets, missing audit trails |
**Gap Identification Process**:
1. **Scan Each View**: Check if view exists, identify missing critical sections, flag [TODO]/[TBD] areas
2. **Cross-View Analysis**: Find inconsistencies (e.g., "high availability" stated but single instance shown)
3. **Risk Severity**: CRITICAL (outage/breach/loss), HIGH (operational burden), MEDIUM (debt), LOW (documentation)
**Gap ID Format**: Section-based (e.g., `3.6.1` = Deployment View, gap #1)
#### Phase 2.6: Constitution Cross-Reference
**Objective**: Check ADRs against constitution for duplication and compliance
**Analysis**:
1. **Duplication Check**: Does ADR restate a constitutional principle? Is decision already mandated?
2. **Compliance Check**: Does decision violate MUST principles? Ignore SHOULD without justification?
3. **Override Classification**: Is this an intentional deviation? Justified sufficiently?
**Constitution Cross-Reference Table**:
| Issue Type | ADR | Constitution Principle | Action Required |
|------------|-----|----------------------|-----------------|
| Duplicate | ADR-002 | §DataStorage mandates PostgreSQL | Remove or convert to reference |
| Violation | ADR-003 | §Security requires JWT | Add override justification or change |
| Unclear | ADR-004 | Silent on caching | Clarify relationship |
#### Phase 3: Gap Identification
**Objective**: Prioritize clarification needs
Generate a gap report:
```markdown
## ADR Clarification Report
### Summary
- Total ADRs: [N]
- Complete: [N]
- Needs Clarification: [N]
### Gaps by ADR
| ADR | Title | Gap Type | Severity | Priority |
|-----|-------|----------|----------|----------|
| ADR-001 | [Title] | Missing alternatives | HIGH | 1 |
| ADR-002 | [Title] | Incomplete consequences | MEDIUM | 2 |
### Cross-ADR Issues
| Issue | ADRs Affected | Description |
|-------|---------------|-------------|
| [Conflict] | ADR-001, ADR-003 | [Description of conflict] |
```
##### Gap Prioritization
- **CRITICAL**: Constitution violations, missing decision statement
- **HIGH**: No alternatives documented, missing consequences
- **MEDIUM**: Incomplete risks, unclear context
- **LOW**: Minor phrasing improvements, optional details
#### Phase 4: Interactive Refinement
**Objective**: Fill gaps through targeted questions
Write every clarification in Simplified Technical English (short sentences, one idea each, active voice, imperative asks, named referents, plain words). Read the contract at `skills/authoring/references/ste-rules.md` before drafting questions.
Count crossed boundaries (`record`, `lane`, `team`, `now`) for each gap. See the tier rule in the shared reference: **0–1 boundaries** use the plain STE prose format below, however severe the gap; **2 or more boundaries** draw the full ASCII decision map from the reference (WHAT/WHY, crossed boundaries, reach fan-out, candidates × discriminating axes, IF-UNDECIDED). Render unknown gaps as `?` slots, never prose complaints.
##### Question Format
For each gap requiring clarification:
```markdown
## Clarification [N]: [ADR-XXX] - [Gap Type]
**Current State**:
[Quote current ADR content]
**Gap Identified**:
[Explain what's missing or unclear]
**Question**:
[Specific question to address the gap]
**Suggested Options** (if applicable):
| Option | Description |
|--------|-------------|
| A | [Option A] |
| B | [Option B] |
| C | [Custom response] |
Reply with your choice or provide additional context.
```
##### Constitution Cross-Reference Questions
When constitution issues are detected:
**For Duplicates**:
```markdown
## Clarification [N]: Constitution Duplication Detected
**Question**: How should this duplicate ADR be handled?
**ADR**: ADR-XXX - [Title]
**Constitution Principle**: §[Section] - [Principle Name]
**Issue**: This ADR documents a decision already mandated by constitution
**Options**:
| Option | Action | Result |
|--------|--------|--------|
| A | Remove ADR | Decision covered by constitution only |
| B | Convert to Reference | Keep ADR as "See Constitution §X" |
| C | Add Context | Keep ADR with "Aligns with Constitution §X" |
| D | Extend | Keep ADR as "Extends Constitution §X" |
Reply with your choice (A/B/C/D).
```
**For Violations (Option A PRIMARY - Amend Constitution)**:
```markdown
## Clarification [N]: Constitution Violation Detected ⭐
**Question**: How should this constitutional violation be resolved?
**ADR**: ADR-XXX - [Title]
**Decision**: [What the ADR decides]
**Constitution Principle**: §[Section] - [Principle]
**Conflict**: [How they conflict]
**The constitution should evolve with the project's needs.**
**⭐ RECOMMENDED: A. Amend Constitution**
Update constitution §[Section] to accommodate this decision. This establishes a new principle for future decisions.
**Alternative Options**:
B. Override in ADR - Document justification for deviation
C. Revise ADR - Change decision to comply with constitution
D. Remove ADR - Delete and follow existing constitution
**Consider Amendment If**:
- [ ] This decision will be used again in the future
- [ ] Team's approach has evolved since constitution was written
- [ ] Existing principle is too restrictive for current needs
**Amendment Text**: [If choosing A, provide the specific constitutional amendment]
Reply with: "A [amendment text]" or "B/C/D [reasoning]"
```
##### Clarification Rules
- Present **one clarification at a time**
- **Prioritize by severity** - address CRITICAL/HIGH gaps first
- **For constitution violations, Option A (Amend) is PRIMARY**
- **Limit to 5 clarifications** per session (increased to 10 if architecture present)
- Allow user to **skip** non-critical clarifications
- **Summarize changes** after each answer
- User can say "done" to end clarification early
#### Phase 5: ADR Updates
**Objective**: Write refined ADRs back to file
1. **Apply Clarifications**:
- Update ADR sections with new content
- Preserve structure and formatting
- Update "Last Updated" timestamps
2. **Resolve Conflicts**:
- If cross-ADR conflicts were found, propose resolution
- Document decision to favor one ADR over another
- Add cross-references between related ADRs
3. **Update Index**:
- Refresh ADR index table if titles changed
- Update status if applicable
4. **Write File**:
- Atomic write to individual `{REPO_ROOT}/.adlc/drafts/adr/ADR-{NNN}.md` files
- Preserve any ADRs that weren't modified
- The setup script auto-regenerates `adr.md` and `adr.md` index after updates
#### Phase 5.5: ADR Approval ⭐
**Objective**: Get user confirmation to approve ADRs before architecture generation
This step is **critical** because `implement` only processes ADRs with "Accepted" status. ADRs with "Discovered" or "Proposed" status will be skipped.
##### Approval Request
```markdown
## ADR Approval ⭐
**Total ADRs**: [N]
**Status Distribution**:
- Accepted: [N]
- Proposed: [N]
- Discovered: [N]
**⚠️ Important**: Only "Accepted" ADRs will be processed by `/architect-implement`
### Options
| Option | Action |
|--------|--------|
| A | Accept All - Change Proposed/Discovered → Accepted |
| B | Review Specific - Select individual ADRs to accept |
| C | Defer - Keep current status, decide later |
**Note**: You can also run `/architect-clarify` again later to approve additional ADRs.
Reply with A, B, or C (or "done" to skip).
```
##### Bulk Approval (Option A)
If user chooses Option A:
```markdown
## Confirm Bulk Approval
Change [N] ADRs from Proposed/Discovered → Accepted?
| ADR | Current Status | New Status |
|-----|----------------|-------------|
| ADR-001 | Proposed | Accepted |
| ADR-002 | Discovered | Accepted |
Reply with "yes" to confirm or "no" to cancel.
```
##### Selective Approval (Option B)
If user chooses Option B, present ADRs one-by-one:
```markdown
## ADR Approval: ADR-XXX
**Title**: [Title]
**Current Status**: [Proposed/Discovered]
**Context**: [Brief summary]
**Options**:
| Option | Action |
|--------|--------|
| A | Accept - Change to "Accepted" |
| B | Keep - Keep current status |
| C | Skip - Move to next ADR |
Reply with A, B, or C for ADR-XXX.
```
##### Post-Approval
After approval (or if user chooses C to defer):
```markdown
## ADRs Approved
**Status Changes Applied**:
- ADR-001: Proposed → Accepted
- ADR-002: Discovered → Accepted
- ADR-007: Kept as Proposed
**Ready for Implementation**:
- Accepted ADRs: [N]
- Pending Approval: [N]
Run `/architect-implement` to generate AD.md from accepted ADRs.
```
##### Post-Approval State Update
After approval, you **MUST** write the workflow state file. Create the directory if it doesn't exist, then write `{REPO_ROOT}/.adlc/architect/state.json`:
```bash
mkdir -p {REPO_ROOT}/.adlc/architect
```
Write this JSON content to the file (replace `[N]` with the actual count of approved ADRs and the timestamp with the current time):
```json
{
"workflow": {
"clarify_completed": true,
"clarify_completed_at": "2024-01-20T10:30:00Z",
"adrs_approved_count": [N],
"implement_started": false,
"implement_started_at": null
}
}
```
**IMPORTANT**: This workflow state file is REQUIRED before `/architect-implement` will execute.
The implement command checks this state during pre-flight validation. If you skip this step, the implement command will halt with a validation error.
### Key Rules
#### Non-Destructive Refinement
- **Never delete** existing ADRs without explicit user approval
- **Preserve original intent** when updating wording
- **Add, don't replace** consequences and alternatives
- **Mark changes** with updated timestamps
#### Focused Clarification
- Ask **one question at a time**
- Make questions **specific and answerable**
- Provide **suggested options** when possible
- Respect user's time - limit to **5 clarifications**
#### Constitution Authority
- Constitution violations are **always flagged**
- ADRs cannot override MUST principles without explicit justification
- Suggest constitution updates if conflict is systemic
#### Quality Over Quantity
- Focus on **material gaps** that affect implementation
- Skip **cosmetic improvements** unless user requests
- **Defer minor issues** if major gaps remain
### Completion Report
After clarification ends (all gaps addressed or user signals "done"):
```markdown
## ADR Clarification Complete
**Changes Made**:
- ADR-001: Updated consequences section
- ADR-002: Added Common Alternatives (neutral trade-offs)
- ADR-003: Resolved conflict with ADR-001
- Constitution: Amended §DataStorage to allow NoSQL for document flexibility
**Risks & Gaps Status**:
- Critical gaps identified: [N] → [N resolved]
- High priority gaps: [N] → [N resolved]
- Cross-view inconsistencies: [N] → [N resolved]
- Integration with AD sections: [N updates]
**Remaining Gaps** (deferred):
- 3.6.2: Minor operational documentation (LOW)
**Cross-ADR Consistency**: ✅ Verified
**Constitution Alignment**:
- Duplicates resolved: [N]
- Violations addressed: [N]
- Constitution amended: [N] (if applicable)
- References added: [N]
**Recommended Next Steps**:
1. Review updated ADRs in `{REPO_ROOT}/.adlc/drafts/adr/adr.md`
2. Run `/architect-implement` to generate AD.md
3. Or run `/architect-specify` to create new ADRs for additional decisions
```
### Workflow Status
**⚠️ Required**: You MUST run `/architect-clarify` before `/architect-implement` to approve ADRs.
**Status Workflow**:
- init → "Discovered" (brownfield)
- specify → "Proposed" (greenfield)
- **clarify → ask to approve → "Accepted"** ⭐
- implement → only reads "Accepted", skips Discovered/Proposed
**Note**: Do NOT invent intermediate statuses like "Validated". ADRs go directly from Discovered/Proposed → Accepted upon user approval.
### Context
$ARGUMENTS
## Next Steps
After `clarify` completes, run `/architect-implement` to generate the Architecture Description (`AD.md`).
Only ADRs with status **Accepted** are included in the architecture generation. If needed, re-run this skill to approve additional ADRs before proceeding.
## Verification
Concrete outputs of this skill:
- ADR files updated at `{REPO_ROOT}/.adlc/drafts/adr/ADR-{NNN}.md` with clarified content
- ADR index refreshed at `{REPO_ROOT}/.adlc/drafts/adr/adr.md`
- Constitution updated if amendments were approved
- Workflow state written to `{REPO_ROOT}/.adlc/architect/state.json` with `clarify_completed: true`
- At least one ADR status changed to **Accepted**, or explicit deferral recorded
- Cross-ADR conflicts resolved or documented
- Gap report showing critical/high gaps addressed