Skip to content

Commit 938ec11

Browse files
authored
Merge pull request #44 from tikalk/feature/sub-system-decomposition
feat: Sub-System Decomposition in architect.specify and architect.init (Issue #43)
2 parents 722321c + b9273c2 commit 938ec11

File tree

5 files changed

+770
-40
lines changed

5 files changed

+770
-40
lines changed

scripts/bash/setup-architecture.sh

Lines changed: 207 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ACTION=""
77
ARGS=()
88
VIEWS="core"
99
ADR_HEURISTIC="surprising"
10+
DECOMPOSE=true
1011

1112
# Parse arguments
1213
while [[ $# -gt 0 ]]; do
@@ -33,6 +34,14 @@ while [[ $# -gt 0 ]]; do
3334
ADR_HEURISTIC="${1#*=}"
3435
shift
3536
;;
37+
--no-decompose)
38+
DECOMPOSE=false
39+
shift
40+
;;
41+
--no-decompose=*)
42+
DECOMPOSE=false
43+
shift
44+
;;
3645
init|map|update|review|specify|implement|clarify)
3746
ACTION="$1"
3847
shift
@@ -53,6 +62,7 @@ while [[ $# -gt 0 ]]; do
5362
echo " --json Output results in JSON format"
5463
echo " --views VIEWS Architecture views to generate: core (default), all, or comma-separated"
5564
echo " --adr-heuristic H ADR generation heuristic: surprising (default), all, minimal"
65+
echo " --no-decompose Disable automatic sub-system decomposition (default: auto-detect)"
5666
echo " --help Show this help message"
5767
echo ""
5868
echo "Examples:"
@@ -101,6 +111,151 @@ AD_TEMPLATE_FILE="$REPO_ROOT/.specify/templates/AD-template.md"
101111
# Export for use in functions
102112
export ARCHITECTURE_VIEWS="$VIEWS"
103113
export ADR_HEURISTIC="$ADR_HEURISTIC"
114+
export DECOMPOSE="$DECOMPOSE"
115+
116+
# Function to detect sub-systems from codebase structure
117+
detect_subsystems() {
118+
local subsystems=""
119+
local count=0
120+
121+
echo "Detecting sub-systems from codebase structure..." >&2
122+
123+
# Check for common sub-system patterns
124+
125+
# 1. Top-level feature directories (src/, app/, services/)
126+
local dirs=()
127+
if [[ -d "src" ]]; then
128+
for d in src/*/; do
129+
if [[ -d "$d" ]]; then
130+
local dirname
131+
dirname=$(basename "$d")
132+
# Skip common non-sub-system directories
133+
if [[ "$dirname" != "utils" && "$dirname" != "common" && "$dirname" != "lib" && "$dirname" != "shared" && "$dirname" != "core" ]]; then
134+
dirs+=("$dirname")
135+
fi
136+
fi
137+
done
138+
fi
139+
140+
if [[ -d "services" ]]; then
141+
for d in services/*/; do
142+
if [[ -d "$d" ]]; then
143+
local dirname
144+
dirname=$(basename "$d")
145+
dirs+=("$dirname")
146+
fi
147+
done
148+
fi
149+
150+
if [[ -d "modules" ]]; then
151+
for d in modules/*/; do
152+
if [[ -d "$d" ]]; then
153+
local dirname
154+
dirname=$(basename "$d")
155+
dirs+=("$dirname")
156+
fi
157+
done
158+
fi
159+
160+
if [[ -d "apps" ]]; then
161+
for d in apps/*/; do
162+
if [[ -d "$d" ]]; then
163+
local dirname
164+
dirname=$(basename "$d")
165+
dirs+=("$dirname")
166+
fi
167+
done
168+
fi
169+
170+
# 2. Check for docker-compose services (microservices indicator)
171+
if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
172+
local compose_file="docker-compose.yml"
173+
[[ -f "docker-compose.yaml" ]] && compose_file="docker-compose.yaml"
174+
175+
local services=()
176+
while IFS= read -r line; do
177+
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+):[[:space:]]*$ ]]; then
178+
local svc="${BASH_REMATCH[1]}"
179+
# Skip common non-service entries
180+
if [[ "$svc" != "version" && "$svc" != "services" && "$svc" != "networks" && "$svc" != "volumes" ]]; then
181+
services+=("$svc")
182+
fi
183+
fi
184+
done < "$compose_file"
185+
186+
for svc in "${services[@]}"; do
187+
local found=false
188+
for d in "${dirs[@]}"; do
189+
if [[ "${d,,}" == *"${svc,,}"* ]] || [[ "${svc,,}" == *"${d,,}"* ]]; then
190+
found=true
191+
break
192+
fi
193+
done
194+
if [[ "$found" == "false" ]]; then
195+
dirs+=("$svc")
196+
fi
197+
done
198+
fi
199+
200+
# 3. Check for Node.js workspaces (monorepo indicator)
201+
if [[ -f "package.json" ]]; then
202+
if grep -q '"workspaces"' package.json 2>/dev/null; then
203+
local pkgs
204+
pkgs=$(node -e "try { const p = require('./package.json'); console.log(Object.keys(p.workspaces?.packages || {}).join(' ')); } catch(e) { }" 2>/dev/null || true)
205+
for pkg in $pkgs; do
206+
local dirname
207+
dirname=$(basename "$pkg")
208+
if [[ "$dirname" != "node_modules" ]]; then
209+
dirs+=("$dirname")
210+
fi
211+
done
212+
fi
213+
fi
214+
215+
# 4. Check for Python namespace packages
216+
if [[ -f "pyproject.toml" ]]; then
217+
local pkg_dirs=()
218+
while IFS= read -r -d '' d; do
219+
pkg_dirs+=("$(basename "$d")")
220+
done < <(find . -maxdepth 3 -name "__init__.py" -printf '%h\n' 2>/dev/null | grep -v node_modules | grep -v __pycache__ | sort -u || true)
221+
222+
for pdir in "${pkg_dirs[@]}"; do
223+
if [[ "$pdir" != "." && "$pdir" != "src" ]]; then
224+
dirs+=("$pdir")
225+
fi
226+
done
227+
fi
228+
229+
# Remove duplicates and build output
230+
local unique_dirs=($(printf '%s\n' "${dirs[@]}" | sort -u))
231+
232+
if [[ ${#unique_dirs[@]} -gt 0 ]]; then
233+
echo "Detected potential sub-systems:" >&2
234+
for d in "${unique_dirs[@]}"; do
235+
((count++))
236+
echo " - $d" >&2
237+
done
238+
echo "Total: $count sub-system(s)" >&2
239+
else
240+
echo "No distinct sub-systems detected from directory structure." >&2
241+
fi
242+
243+
# Return as JSON if JSON mode
244+
if $JSON_MODE; then
245+
echo "["
246+
local first=true
247+
for d in "${unique_dirs[@]}"; do
248+
if [[ "$first" == "true" ]]; then
249+
first=false
250+
else
251+
echo ","
252+
fi
253+
echo -n " {\"id\": \"$d\", \"name\": \"$d\", \"detection_method\": \"directory\", \"evidence\": \"directory: $d/\"}"
254+
done
255+
echo ""
256+
echo "]"
257+
fi
258+
}
104259

105260
# Function to detect tech stack from codebase
106261
detect_tech_stack() {
@@ -444,6 +599,17 @@ action_specify() {
444599
# Ensure memory directory exists
445600
mkdir -p "$REPO_ROOT/.specify/memory"
446601

602+
# Show decomposition status
603+
if [[ "$DECOMPOSE" == "true" ]]; then
604+
echo "" >&2
605+
echo "🔄 Sub-system decomposition: ENABLED" >&2
606+
echo " (AI agent will detect domains from PRD and propose sub-systems)" >&2
607+
else
608+
echo "" >&2
609+
echo "⚠️ Sub-system decomposition: DISABLED (--no-decompose flag)" >&2
610+
echo " (AI agent will generate monolithic ADRs)" >&2
611+
fi
612+
447613
# Initialize ADR file from template if it doesn't exist
448614
if [[ ! -f "$adr_file" ]]; then
449615
if [[ -f "$adr_template" ]]; then
@@ -457,8 +623,8 @@ action_specify() {
457623
458624
## ADR Index
459625
460-
| ID | Decision | Status | Date | Owner |
461-
|----|----------|--------|------|-------|
626+
| ID | Sub-System | Decision | Status | Date | Owner |
627+
|----|------------|----------|--------|------|-------|
462628
463629
---
464630
@@ -472,15 +638,22 @@ EOF
472638
echo "" >&2
473639
echo "Ready for interactive PRD exploration." >&2
474640
echo "The AI agent will:" >&2
641+
if [[ "$DECOMPOSE" == "true" ]]; then
642+
echo " 0. (Phase 0) Detect domains in PRD and propose sub-systems" >&2
643+
echo " → Ask user to confirm sub-system breakdown" >&2
644+
fi
475645
echo " 1. Analyze your PRD/requirements input" >&2
476646
echo " 2. Ask clarifying questions about architecture" >&2
477647
echo " 3. Create ADRs for each key decision" >&2
478648
echo " 4. Save decisions to .specify/memory/adr.md" >&2
649+
if [[ "$DECOMPOSE" == "true" ]]; then
650+
echo " 5. Organize ADRs by sub-system" >&2
651+
fi
479652
echo "" >&2
480653
echo "After completion, run '/architect.implement' to generate full AD.md" >&2
481654

482655
if $JSON_MODE; then
483-
echo "{\"status\":\"success\",\"action\":\"specify\",\"adr_file\":\"$adr_file\",\"context\":\"${ARGS[*]}\"}"
656+
echo "{\"status\":\"success\",\"action\":\"specify\",\"adr_file\":\"$adr_file\",\"context\":\"${ARGS[*]}\",\"decomposition\":\"$DECOMPOSE\"}"
484657
fi
485658
}
486659

@@ -589,6 +762,26 @@ action_init() {
589762
local dir_structure
590763
dir_structure=$(map_directory_structure)
591764

765+
# Phase 0: Sub-system detection (if decomposition enabled)
766+
local subsystems_json=""
767+
local decompose_status="disabled"
768+
769+
if [[ "$DECOMPOSE" == "true" ]]; then
770+
echo "" >&2
771+
echo "🔄 Phase 0: Sub-System Detection" >&2
772+
subsystems_json=$(detect_subsystems)
773+
decompose_status="enabled"
774+
775+
if [[ -n "$subsystems_json" ]] && [[ "$subsystems_json" != "[]" ]]; then
776+
echo "" >&2
777+
echo "📦 Sub-systems will be used to organize ADRs" >&2
778+
echo " (AI agent will confirm with user before proceeding)" >&2
779+
fi
780+
else
781+
echo "" >&2
782+
echo "⚠️ Sub-system decomposition disabled (--no-decompose flag)" >&2
783+
fi
784+
592785
# Initialize ADR file from template if it doesn't exist
593786
if [[ ! -f "$adr_file" ]]; then
594787
if [[ -f "$adr_template" ]]; then
@@ -602,8 +795,8 @@ action_init() {
602795
603796
## ADR Index
604797
605-
| ID | Decision | Status | Date | Owner |
606-
|----|----------|--------|------|-------|
798+
| ID | Sub-System | Decision | Status | Date | Owner |
799+
|----|------------|----------|--------|------|-------|
607800
608801
---
609802
@@ -623,16 +816,23 @@ EOF
623816
echo "" >&2
624817
echo "Ready for brownfield architecture discovery." >&2
625818
echo "The AI agent will:" >&2
819+
if [[ "$DECOMPOSE" == "true" ]]; then
820+
echo " 0. (Phase 0) Propose sub-systems from code structure" >&2
821+
echo " → Ask user to confirm sub-system breakdown" >&2
822+
fi
626823
echo " 1. Analyze codebase structure and patterns" >&2
627824
echo " 2. Infer architectural decisions from code" >&2
628825
echo " 3. Create ADRs marked as 'Discovered (Inferred)'" >&2
629-
echo " 4. Auto-trigger /architect.clarify to validate findings" >&2
826+
if [[ "$DECOMPOSE" == "true" ]]; then
827+
echo " 4. Organize ADRs by sub-system" >&2
828+
fi
829+
echo " 5. Auto-trigger /architect.clarify to validate findings" >&2
630830
echo "" >&2
631831
echo "NOTE: AD.md will NOT be created until ADRs are validated." >&2
632832
echo " After clarification, run /architect.implement to generate AD.md" >&2
633833

634834
if $JSON_MODE; then
635-
echo "{\"status\":\"success\",\"action\":\"init\",\"adr_file\":\"$adr_file\",\"tech_stack\":\"$tech_stack\",\"existing_docs\":\"$existing_docs\",\"source\":\"brownfield\"}"
835+
echo "{\"status\":\"success\",\"action\":\"init\",\"adr_file\":\"$adr_file\",\"tech_stack\":\"$tech_stack\",\"existing_docs\":\"$existing_docs\",\"source\":\"brownfield\",\"decomposition\":\"$decompose_status\",\"subsystems\":$subsystems_json}"
636836
fi
637837
}
638838

0 commit comments

Comments
 (0)