create-living-architecture 1.0.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.living-arch/SYSTEM.md +26 -0
- package/.living-arch/architecture.json +6 -0
- package/README.md +535 -0
- package/installer.js +42 -0
- package/package.json +10 -25
- package/src/app/__init__.py +16 -0
- package/src/app/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/app/__pycache__/validation-workflow.cpython-314.pyc +0 -0
- package/src/app/installer-workflow.js +56 -0
- package/src/app/validation-workflow.py +184 -0
- package/src/config/SYSTEM.md +58 -0
- package/src/config/changes.json +11 -0
- package/src/config/execution.json +9 -0
- package/src/config/f-tags.json +12 -0
- package/src/config/interfaces.json +16 -0
- package/src/config/modules.json +19 -0
- package/src/config/operations.json +272 -0
- package/src/config/r-layers.json +111 -0
- package/src/contract/cli/la-new.sh +64 -0
- package/src/contract/cli/network-scan.py +195 -0
- package/src/contract/hooks/commit-msg +17 -0
- package/src/contract/hooks/pre-commit +4 -0
- package/src/domain/__init__.py +20 -0
- package/src/domain/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/config_loader.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/la-output.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/validate-changes.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/validate-execution.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/validate-f-tags.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/validate-operations.cpython-314.pyc +0 -0
- package/src/domain/__pycache__/validate-r-layers.cpython-314.pyc +0 -0
- package/src/domain/config_loader.py +84 -0
- package/src/domain/installer-logic.js +30 -0
- package/src/domain/la-output.py +46 -0
- package/src/domain/validate-changes.py +60 -0
- package/src/domain/validate-execution.py +91 -0
- package/src/domain/validate-f-tags.py +93 -0
- package/src/domain/validate-operations.py +100 -0
- package/src/domain/validate-r-layers.py +202 -0
- package/src/exec/__init__.py +21 -0
- package/src/exec/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/exec/__pycache__/file-io.cpython-314.pyc +0 -0
- package/src/exec/__pycache__/git-io.cpython-314.pyc +0 -0
- package/src/exec/__pycache__/module-loader.cpython-314.pyc +0 -0
- package/src/exec/file-io.py +99 -0
- package/src/exec/git-io.py +59 -0
- package/src/exec/installer-io.js +36 -0
- package/src/exec/module-loader.py +29 -0
- package/index.js +0 -79
- package/templates/.living-arch/SYSTEM.md +0 -53
- package/templates/ARCHITECTURE.md +0 -56
- package/templates/README.md +0 -28
- package/templates/src/api/README.md +0 -13
- package/templates/src/database/README.md +0 -13
- package/templates/src/domain/README.md +0 -12
- package/templates/src/integrations/README.md +0 -13
- package/templates/starter-template/ARCHITECTURE.md +0 -56
- package/templates/starter-template/README.md +0 -28
- package/templates/starter-template/src/api/README.md +0 -13
- package/templates/starter-template/src/database/README.md +0 -13
- package/templates/starter-template/src/domain/README.md +0 -12
- package/templates/starter-template/src/integrations/README.md +0 -13
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"enforcement_mode": "blocking",
|
|
4
|
+
"layers": {
|
|
5
|
+
"R0": {
|
|
6
|
+
"name": "Config",
|
|
7
|
+
"directory": "config",
|
|
8
|
+
"required": false
|
|
9
|
+
},
|
|
10
|
+
"R1": {
|
|
11
|
+
"name": "Domain",
|
|
12
|
+
"directory": "domain",
|
|
13
|
+
"required": false
|
|
14
|
+
},
|
|
15
|
+
"R2": {
|
|
16
|
+
"name": "Application",
|
|
17
|
+
"directory": "app",
|
|
18
|
+
"required": false
|
|
19
|
+
},
|
|
20
|
+
"R3": {
|
|
21
|
+
"name": "Contract",
|
|
22
|
+
"directory": "contract",
|
|
23
|
+
"required": true
|
|
24
|
+
},
|
|
25
|
+
"R4": {
|
|
26
|
+
"name": "Execution",
|
|
27
|
+
"directory": "exec",
|
|
28
|
+
"required": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"R0": {
|
|
33
|
+
"allowed": [],
|
|
34
|
+
"forbidden": ["R1", "R2", "R3", "R4"]
|
|
35
|
+
},
|
|
36
|
+
"R1": {
|
|
37
|
+
"allowed": ["R0"],
|
|
38
|
+
"forbidden": ["R2", "R3", "R4"]
|
|
39
|
+
},
|
|
40
|
+
"R2": {
|
|
41
|
+
"allowed": ["R0", "R1", "R4"],
|
|
42
|
+
"forbidden": ["R3"]
|
|
43
|
+
},
|
|
44
|
+
"R3": {
|
|
45
|
+
"allowed": ["R0", "R1", "R2"],
|
|
46
|
+
"forbidden": ["R4"]
|
|
47
|
+
},
|
|
48
|
+
"R4": {
|
|
49
|
+
"allowed": ["R0"],
|
|
50
|
+
"forbidden": ["R1", "R2", "R3"]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"skip_matrix": {
|
|
54
|
+
"R0": {
|
|
55
|
+
"add_when": "configuration_concerns > 5",
|
|
56
|
+
"skip_if": "all config fits in .env"
|
|
57
|
+
},
|
|
58
|
+
"R1": {
|
|
59
|
+
"add_when": "business_rules > 10",
|
|
60
|
+
"skip_if": "simple CRUD app"
|
|
61
|
+
},
|
|
62
|
+
"R2": {
|
|
63
|
+
"add_when": "workflows > 3 or state_machines",
|
|
64
|
+
"skip_if": "direct handlers work"
|
|
65
|
+
},
|
|
66
|
+
"R3": {
|
|
67
|
+
"add_when": "always",
|
|
68
|
+
"skip_if": "never"
|
|
69
|
+
},
|
|
70
|
+
"R4": {
|
|
71
|
+
"add_when": "always",
|
|
72
|
+
"skip_if": "never"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"evolution_phases": [
|
|
76
|
+
{
|
|
77
|
+
"phase": "tiny",
|
|
78
|
+
"active": ["R3", "R4"]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"phase": "small",
|
|
82
|
+
"active": ["R1", "R2", "R3", "R4"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"phase": "growing",
|
|
86
|
+
"active": ["R0", "R1", "R2", "R3", "R4"]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"phase": "enterprise",
|
|
90
|
+
"active": ["R0", "R1", "R2", "R3", "R4"]
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"commit_format": {
|
|
94
|
+
"pattern": "^\\[F-[a-z][a-z0-9-]*/R[0-4]/C[1-5]\\]",
|
|
95
|
+
"layer_codes": {
|
|
96
|
+
"R0": "Config",
|
|
97
|
+
"R1": "Domain",
|
|
98
|
+
"R2": "Application",
|
|
99
|
+
"R3": "Contract",
|
|
100
|
+
"R4": "Execution"
|
|
101
|
+
},
|
|
102
|
+
"classification_codes": {
|
|
103
|
+
"C1": "Internal",
|
|
104
|
+
"C2": "Feature",
|
|
105
|
+
"C3": "Contract",
|
|
106
|
+
"C4": "Structure",
|
|
107
|
+
"C5": "Migration"
|
|
108
|
+
},
|
|
109
|
+
"feature_tag": "required, F-kebab-case, groups commits into vertical slices"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# la-new.sh - R3 Contract (local installer)
|
|
4
|
+
# Usage: ./la-new.sh <project-name>
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
LA_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
8
|
+
|
|
9
|
+
BORDER="════════════════════════════════════════════════════════════"
|
|
10
|
+
|
|
11
|
+
if [ -z "$1" ]; then
|
|
12
|
+
echo "Usage: ./la-new.sh <project-name>"
|
|
13
|
+
echo "Example: ./la-new.sh my-app"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
PROJECT_NAME="$1"
|
|
18
|
+
|
|
19
|
+
if [ -d "$PROJECT_NAME" ]; then
|
|
20
|
+
echo "✗ Directory '$PROJECT_NAME' already exists"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo ""
|
|
25
|
+
echo "$BORDER"
|
|
26
|
+
printf "%*s\n" $(( (${#BORDER} + 18) / 2 )) "LIVING ARCHITECTURE"
|
|
27
|
+
echo "$BORDER"
|
|
28
|
+
echo ""
|
|
29
|
+
echo " Creating project: $PROJECT_NAME"
|
|
30
|
+
|
|
31
|
+
# Copy LA structure
|
|
32
|
+
mkdir -p "$PROJECT_NAME"
|
|
33
|
+
cp -r "$LA_DIR/src" "$PROJECT_NAME/src"
|
|
34
|
+
cp -r "$LA_DIR/.living-arch" "$PROJECT_NAME/.living-arch"
|
|
35
|
+
|
|
36
|
+
# Init git
|
|
37
|
+
git init "$PROJECT_NAME" --quiet
|
|
38
|
+
|
|
39
|
+
# Hook up pre-commit and commit-msg
|
|
40
|
+
chmod +x "$PROJECT_NAME/src/contract/hooks/pre-commit"
|
|
41
|
+
chmod +x "$PROJECT_NAME/src/contract/hooks/commit-msg"
|
|
42
|
+
|
|
43
|
+
ln -sf "$(pwd)/$PROJECT_NAME/src/contract/hooks/pre-commit" \
|
|
44
|
+
"$PROJECT_NAME/.git/hooks/pre-commit"
|
|
45
|
+
|
|
46
|
+
ln -sf "$(pwd)/$PROJECT_NAME/src/contract/hooks/commit-msg" \
|
|
47
|
+
"$PROJECT_NAME/.git/hooks/commit-msg"
|
|
48
|
+
|
|
49
|
+
# Initial commit — skip validation, this is scaffolding not app code
|
|
50
|
+
cd "$PROJECT_NAME"
|
|
51
|
+
git add .
|
|
52
|
+
git commit --no-verify -m "LA v2.0 scaffold" --quiet
|
|
53
|
+
cd ..
|
|
54
|
+
|
|
55
|
+
echo ""
|
|
56
|
+
echo " ✓ Project created: $PROJECT_NAME/"
|
|
57
|
+
echo " ✓ Git initialized"
|
|
58
|
+
echo " ✓ Hooks installed"
|
|
59
|
+
echo " ✓ Initial commit done"
|
|
60
|
+
echo ""
|
|
61
|
+
echo " Ready: cd $PROJECT_NAME"
|
|
62
|
+
echo ""
|
|
63
|
+
echo "$BORDER"
|
|
64
|
+
echo ""
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
network-scan.py - R3 CLI Interface
|
|
4
|
+
Project health scanning with progressive zoom
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
network-scan.py # All features
|
|
8
|
+
network-scan.py F-counter # Feature detail
|
|
9
|
+
network-scan.py F-counter R1 # Layer detail
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
|
|
15
|
+
|
|
16
|
+
from domain import validate_f_tags, la_output
|
|
17
|
+
from app import validation_workflow
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
|
|
21
|
+
# F-tags to exclude from feature scans (infrastructure, not features)
|
|
22
|
+
EXCLUDED_TAGS = {'F-la'}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _git(args):
|
|
26
|
+
"""Run a git command, return stripped stdout or empty string."""
|
|
27
|
+
try:
|
|
28
|
+
r = subprocess.run(['git'] + args, capture_output=True, text=True)
|
|
29
|
+
return r.stdout.strip()
|
|
30
|
+
except Exception:
|
|
31
|
+
return ''
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_commits_with_ftag_and_layer(f_tag, r_layer):
|
|
35
|
+
pattern = f'\\[{f_tag}/{r_layer}/'
|
|
36
|
+
out = _git(['log', '--all', '--pretty=format:%h %s', '--grep', pattern])
|
|
37
|
+
return [l for l in out.split('\n') if l] if out else []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_commits_with_ftag(f_tag):
|
|
41
|
+
out = _git(['log', '--all', '--grep', f_tag, '--oneline'])
|
|
42
|
+
return [l for l in out.split('\n') if l] if out else []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_files_for_ftag_and_layer(f_tag, r_layer):
|
|
46
|
+
pattern = f'\\[{f_tag}/{r_layer}/'
|
|
47
|
+
out = _git(['log', '--all', '--pretty=format:', '--name-only', '--grep', pattern])
|
|
48
|
+
if out:
|
|
49
|
+
return sorted({l.strip() for l in out.split('\n') if l.strip()})
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_all_ftags():
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
['git', 'log', '--all', '--pretty=format:%s'],
|
|
57
|
+
capture_output=True, text=True
|
|
58
|
+
)
|
|
59
|
+
f_tags = set()
|
|
60
|
+
for line in result.stdout.split('\n'):
|
|
61
|
+
match = re.search(r'\[F-([a-z][a-z0-9-]*)/R[0-4]/C[1-5]\]', line)
|
|
62
|
+
if match:
|
|
63
|
+
tag = f"F-{match.group(1)}"
|
|
64
|
+
if tag not in EXCLUDED_TAGS:
|
|
65
|
+
f_tags.add(tag)
|
|
66
|
+
return sorted(f_tags)
|
|
67
|
+
except:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def scan_all_features():
|
|
72
|
+
print(la_output.header('NETWORK SCAN'))
|
|
73
|
+
|
|
74
|
+
f_tags = get_all_ftags()
|
|
75
|
+
|
|
76
|
+
if not f_tags:
|
|
77
|
+
print(" No features detected")
|
|
78
|
+
else:
|
|
79
|
+
for f_tag in f_tags:
|
|
80
|
+
result = validate_f_tags.validate_ftag_completeness(f_tag)
|
|
81
|
+
if result['complete']:
|
|
82
|
+
print(f" {f_tag:20}{la_output.ok('complete')}")
|
|
83
|
+
else:
|
|
84
|
+
missing = ', '.join(result['layers_missing'])
|
|
85
|
+
print(f" {f_tag:20}{la_output.warn(f'missing {missing}')}")
|
|
86
|
+
|
|
87
|
+
print()
|
|
88
|
+
print(f" {len(f_tags)} feature{'s' if len(f_tags) != 1 else ''} detected")
|
|
89
|
+
print()
|
|
90
|
+
print(la_output.divider())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def scan_feature(f_tag):
|
|
94
|
+
print(la_output.header(f'{f_tag.upper()} FEATURE'))
|
|
95
|
+
|
|
96
|
+
result = validate_f_tags.validate_ftag_completeness(f_tag)
|
|
97
|
+
|
|
98
|
+
# R-layers
|
|
99
|
+
if result['complete']:
|
|
100
|
+
print(f" {'R-layers':14}{la_output.ok('complete')}")
|
|
101
|
+
else:
|
|
102
|
+
missing = ', '.join(result['layers_missing'])
|
|
103
|
+
present = len(result['layers_present'])
|
|
104
|
+
total = len(result['layers_required'])
|
|
105
|
+
print(f" {'R-layers':14}{la_output.warn(f'{present}/{total} — missing {missing}')}")
|
|
106
|
+
|
|
107
|
+
# Execution + QC — get real files for this feature, run validators via R2
|
|
108
|
+
feature_files = []
|
|
109
|
+
for layer in result['layers_present']:
|
|
110
|
+
feature_files.extend(_get_files_for_ftag_and_layer(f_tag, layer))
|
|
111
|
+
feature_files = sorted(set(feature_files))
|
|
112
|
+
|
|
113
|
+
val = validation_workflow.validate_files(feature_files)
|
|
114
|
+
exec_val = val['execution']
|
|
115
|
+
qc_val = val['qc']
|
|
116
|
+
|
|
117
|
+
print(f" {'Execution':14}{la_output.ok('') if exec_val['valid'] else la_output.err('')}")
|
|
118
|
+
if qc_val['valid']:
|
|
119
|
+
print(f" {'QC':14}{la_output.ok('')}")
|
|
120
|
+
else:
|
|
121
|
+
violations = qc_val.get('violations', [])
|
|
122
|
+
print(f" {'QC':14}{la_output.err(violations[0] if violations else 'violation')}")
|
|
123
|
+
|
|
124
|
+
# Activity
|
|
125
|
+
total_commits = _get_commits_with_ftag(f_tag)
|
|
126
|
+
n = len(total_commits)
|
|
127
|
+
print(f" {'Activity':14}{n} commit{'s' if n != 1 else ''}")
|
|
128
|
+
|
|
129
|
+
print()
|
|
130
|
+
|
|
131
|
+
# Layer drill-down hints
|
|
132
|
+
for layer in sorted(result['layers_present']):
|
|
133
|
+
commits = _get_commits_with_ftag_and_layer(f_tag, layer)
|
|
134
|
+
print(f" {layer:14}{len(commits)} commit{'s' if len(commits) != 1 else ''}"
|
|
135
|
+
f" → network-scan.py {f_tag} {layer}")
|
|
136
|
+
|
|
137
|
+
print()
|
|
138
|
+
print(la_output.divider())
|
|
139
|
+
def get_layer_detail(f_tag, r_layer):
|
|
140
|
+
"""Get files and commits for a specific F-tag + R-layer."""
|
|
141
|
+
commits = _get_commits_with_ftag_and_layer(f_tag, r_layer)
|
|
142
|
+
files = _get_files_for_ftag_and_layer(f_tag, r_layer)
|
|
143
|
+
return {'f_tag': f_tag, 'r_layer': r_layer, 'commits': commits, 'files': files}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def scan_layer(f_tag, r_layer):
|
|
147
|
+
print(la_output.header(f'{f_tag.upper()} / {r_layer}'))
|
|
148
|
+
|
|
149
|
+
detail = get_layer_detail(f_tag, r_layer)
|
|
150
|
+
|
|
151
|
+
# Files
|
|
152
|
+
if detail['files']:
|
|
153
|
+
print(f" {'Files':14}")
|
|
154
|
+
for f in detail['files']:
|
|
155
|
+
print(f" {f}")
|
|
156
|
+
else:
|
|
157
|
+
print(f" {'Files':14}none found")
|
|
158
|
+
|
|
159
|
+
print()
|
|
160
|
+
|
|
161
|
+
# Commits
|
|
162
|
+
if detail['commits']:
|
|
163
|
+
print(f" {'Commits':14}")
|
|
164
|
+
for c in detail['commits']:
|
|
165
|
+
print(f" {c}")
|
|
166
|
+
else:
|
|
167
|
+
print(f" {'Commits':14}none found")
|
|
168
|
+
|
|
169
|
+
print()
|
|
170
|
+
print(la_output.divider())
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main():
|
|
174
|
+
if len(sys.argv) == 1:
|
|
175
|
+
scan_all_features()
|
|
176
|
+
elif len(sys.argv) == 2:
|
|
177
|
+
f_tag = sys.argv[1]
|
|
178
|
+
if not f_tag.startswith('F-'):
|
|
179
|
+
f_tag = f'F-{f_tag}'
|
|
180
|
+
scan_feature(f_tag)
|
|
181
|
+
elif len(sys.argv) == 3:
|
|
182
|
+
f_tag = sys.argv[1]
|
|
183
|
+
r_layer = sys.argv[2].upper()
|
|
184
|
+
if not f_tag.startswith('F-'):
|
|
185
|
+
f_tag = f'F-{f_tag}'
|
|
186
|
+
scan_layer(f_tag, r_layer)
|
|
187
|
+
else:
|
|
188
|
+
print("Usage:")
|
|
189
|
+
print(" network-scan.py # All features")
|
|
190
|
+
print(" network-scan.py F-counter # Feature detail")
|
|
191
|
+
print(" network-scan.py F-counter R1 # Layer detail")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# commit-msg - Living Architecture validation hook
|
|
4
|
+
# Receives commit message file as $1, validates format and F-tags
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
cd "$(git rev-parse --show-toplevel)" || exit 1
|
|
8
|
+
|
|
9
|
+
if [ ! -f "src/app/validation-workflow.py" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Pass message file path so workflow can read it
|
|
14
|
+
export LA_COMMIT_MSG_FILE="$1"
|
|
15
|
+
|
|
16
|
+
python3 src/app/validation-workflow.py
|
|
17
|
+
exit $?
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""R1 Domain package — auto-discovers .py modules
|
|
2
|
+
Bootstrap exception: imports module_loader from R4 (exec) to load hyphenated filenames.
|
|
3
|
+
This is a package-init-only exception; no other R1 file may import from R4.
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from exec import module_loader
|
|
8
|
+
|
|
9
|
+
_dir = Path(__file__).parent
|
|
10
|
+
|
|
11
|
+
if str(_dir) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_dir))
|
|
13
|
+
|
|
14
|
+
# Auto-discover all .py files (except __init__)
|
|
15
|
+
for _f in sorted(_dir.glob('*.py')):
|
|
16
|
+
if _f.stem == '__init__':
|
|
17
|
+
continue
|
|
18
|
+
_name = _f.stem.replace('-', '_')
|
|
19
|
+
_mod = module_loader.load_module(f'domain.{_name}', _f)
|
|
20
|
+
globals()[_name] = _mod
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
config_loader.py - R1 Helper
|
|
4
|
+
Loads configuration from R0 config files
|
|
5
|
+
Validators use this to stay config-driven
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_r_layers():
|
|
13
|
+
"""Load R-layer definitions from config"""
|
|
14
|
+
config_path = Path(__file__).parent.parent / 'config' / 'r-layers.json'
|
|
15
|
+
with open(config_path) as f:
|
|
16
|
+
config = json.load(f)
|
|
17
|
+
return config['layers']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_valid_r_layer_ids():
|
|
21
|
+
"""Get list of valid R-layer IDs"""
|
|
22
|
+
layers = load_r_layers()
|
|
23
|
+
return list(layers.keys())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_f_tag_rules():
|
|
27
|
+
"""Load F-tag format and completeness rules"""
|
|
28
|
+
config_path = Path(__file__).parent.parent / 'config' / 'f-tags.json'
|
|
29
|
+
with open(config_path) as f:
|
|
30
|
+
return json.load(f)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_c_codes():
|
|
34
|
+
"""Load C-code definitions"""
|
|
35
|
+
config_path = Path(__file__).parent.parent / 'config' / 'changes.json'
|
|
36
|
+
with open(config_path) as f:
|
|
37
|
+
return json.load(f)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_valid_c_codes():
|
|
41
|
+
"""Get list of valid C-codes"""
|
|
42
|
+
config = load_c_codes()
|
|
43
|
+
return list(config['codes'].keys())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_o_rules():
|
|
47
|
+
"""Load O-rule definitions"""
|
|
48
|
+
config_path = Path(__file__).parent.parent / 'config' / 'operations.json'
|
|
49
|
+
with open(config_path) as f:
|
|
50
|
+
return json.load(f)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_module_config():
|
|
54
|
+
"""Load module configuration"""
|
|
55
|
+
config_path = Path(__file__).parent.parent / 'config' / 'modules.json'
|
|
56
|
+
with open(config_path) as f:
|
|
57
|
+
return json.load(f)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_module_from_filepath(filepath):
|
|
61
|
+
"""
|
|
62
|
+
Extract module name from filepath using naming convention.
|
|
63
|
+
Convention: {module}-{component}.ext
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
auth-login.py → "M-auth"
|
|
67
|
+
payment-process.py → "M-payment"
|
|
68
|
+
user.py → None (no module prefix)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: Module name (e.g., "M-auth") or None if no convention used
|
|
72
|
+
"""
|
|
73
|
+
# Check if module detection is enabled
|
|
74
|
+
config = load_module_config()
|
|
75
|
+
if not config.get('enabled', False):
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
filename = Path(filepath).stem # "auth-login.py" → "auth-login"
|
|
79
|
+
|
|
80
|
+
if '-' in filename:
|
|
81
|
+
module = filename.split('-')[0]
|
|
82
|
+
return f"M-{module}"
|
|
83
|
+
|
|
84
|
+
return None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* installer-logic.js - R1 Domain
|
|
3
|
+
* Pure scaffold logic. No I/O. Determines what to create and where.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function validateProjectName(name) {
|
|
7
|
+
if (!name || typeof name !== 'string') {
|
|
8
|
+
return { valid: false, error: 'Project name required' };
|
|
9
|
+
}
|
|
10
|
+
if (!/^[a-z0-9-_]+$/i.test(name)) {
|
|
11
|
+
return { valid: false, error: 'Project name must be alphanumeric (hyphens/underscores allowed)' };
|
|
12
|
+
}
|
|
13
|
+
return { valid: true };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildScaffoldPlan(projectName, laSourceDir) {
|
|
17
|
+
return {
|
|
18
|
+
projectName,
|
|
19
|
+
targetDir: projectName,
|
|
20
|
+
laSourceDir,
|
|
21
|
+
hooks: {
|
|
22
|
+
preCommit: {
|
|
23
|
+
src: `${laSourceDir}/src/contract/hooks/pre-commit`,
|
|
24
|
+
dest: `${projectName}/.git/hooks/pre-commit`
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { validateProjectName, buildScaffoldPlan };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
la-output.py - R1 Domain
|
|
4
|
+
Standard output formatting for all LA interfaces.
|
|
5
|
+
Single source of truth for borders, colors, width.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
WIDTH = 60
|
|
9
|
+
BORDER = '═' * WIDTH
|
|
10
|
+
|
|
11
|
+
# ANSI colors
|
|
12
|
+
PURPLE = '\033[94m'
|
|
13
|
+
BOLD = '\033[1m'
|
|
14
|
+
GREEN = '\033[92m'
|
|
15
|
+
YELLOW = '\033[93m'
|
|
16
|
+
RED = '\033[91m'
|
|
17
|
+
RESET = '\033[0m'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def header(text):
|
|
21
|
+
"""Centered header with double-line borders."""
|
|
22
|
+
return (
|
|
23
|
+
f"\n{PURPLE}{BOLD}{BORDER}{RESET}\n"
|
|
24
|
+
f"{PURPLE}{BOLD}{text:^{WIDTH}}{RESET}\n"
|
|
25
|
+
f"{PURPLE}{BOLD}{BORDER}{RESET}\n"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def divider():
|
|
30
|
+
return f"{PURPLE}{BORDER}{RESET}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ok(text):
|
|
34
|
+
return f"{GREEN}✓{RESET} {text}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def warn(text):
|
|
38
|
+
return f"{YELLOW}⚠{RESET} {text}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def err(text):
|
|
42
|
+
return f"{RED}✗{RESET} {text}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def bold(text):
|
|
46
|
+
return f"{BOLD}{text}{RESET}"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
validate-changes.py - R1 C-code Validator
|
|
4
|
+
Validates C-code format in commit messages
|
|
5
|
+
Config-driven: reads valid codes from src/config/changes.json
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from config_loader import get_valid_c_codes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_c_code_format(commit_message):
|
|
14
|
+
"""
|
|
15
|
+
Validate that commit message has valid C-code.
|
|
16
|
+
Only checks format (C1-C5), not semantic correctness.
|
|
17
|
+
"""
|
|
18
|
+
# Extract C-code from message
|
|
19
|
+
match = re.search(r'/C([1-5])\]', commit_message)
|
|
20
|
+
|
|
21
|
+
if not match:
|
|
22
|
+
return {
|
|
23
|
+
'valid': False,
|
|
24
|
+
'error': 'No C-code found in format [F-*/R*/C#]'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
c_code = f"C{match.group(1)}"
|
|
28
|
+
|
|
29
|
+
# Check against config
|
|
30
|
+
valid_codes = get_valid_c_codes()
|
|
31
|
+
|
|
32
|
+
if c_code not in valid_codes:
|
|
33
|
+
return {
|
|
34
|
+
'valid': False,
|
|
35
|
+
'error': f'Invalid C-code: {c_code}. Must be one of: {", ".join(valid_codes)}'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
'valid': True,
|
|
40
|
+
'c_code': c_code
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def validate(commit_message):
|
|
45
|
+
"""Main validation function (config-driven)"""
|
|
46
|
+
return validate_c_code_format(commit_message)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
if len(sys.argv) > 1:
|
|
51
|
+
message = sys.argv[1]
|
|
52
|
+
result = validate(message)
|
|
53
|
+
|
|
54
|
+
if result['valid']:
|
|
55
|
+
print(f"✓ C-code valid: {result['c_code']}")
|
|
56
|
+
else:
|
|
57
|
+
print(f"✗ {result['error']}")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
else:
|
|
60
|
+
print("Usage: validate-changes.py '<commit message>'")
|