lovarch-cli 0.2.1__py3-none-any.whl
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.
- lovarch_cli/__init__.py +16 -0
- lovarch_cli/__main__.py +10 -0
- lovarch_cli/ai/__init__.py +21 -0
- lovarch_cli/ai/gateway.py +240 -0
- lovarch_cli/api.py +111 -0
- lovarch_cli/auth/__init__.py +32 -0
- lovarch_cli/auth/keyring_store.py +214 -0
- lovarch_cli/auth/local_server.py +165 -0
- lovarch_cli/auth/pkce.py +57 -0
- lovarch_cli/auth/session.py +189 -0
- lovarch_cli/cli.py +262 -0
- lovarch_cli/clients/__init__.py +33 -0
- lovarch_cli/clients/factory.py +54 -0
- lovarch_cli/clients/local_client.py +432 -0
- lovarch_cli/clients/lovarch_storage.py +174 -0
- lovarch_cli/clients/lovarch_supabase.py +295 -0
- lovarch_cli/clients/persistence.py +166 -0
- lovarch_cli/clients/storage.py +66 -0
- lovarch_cli/commands/__init__.py +10 -0
- lovarch_cli/commands/account.py +172 -0
- lovarch_cli/commands/audit.py +394 -0
- lovarch_cli/commands/config_cmd.py +80 -0
- lovarch_cli/commands/consolidate.py +217 -0
- lovarch_cli/commands/context_cmd.py +73 -0
- lovarch_cli/commands/dev.py +287 -0
- lovarch_cli/commands/do_cmd.py +120 -0
- lovarch_cli/commands/init.py +218 -0
- lovarch_cli/commands/jobs_cmd.py +95 -0
- lovarch_cli/commands/login.py +202 -0
- lovarch_cli/commands/mcp_cmd.py +26 -0
- lovarch_cli/commands/run.py +375 -0
- lovarch_cli/commands/signup.py +185 -0
- lovarch_cli/commands/status.py +243 -0
- lovarch_cli/commands/upgrade.py +108 -0
- lovarch_cli/commands/verifica_cmd.py +174 -0
- lovarch_cli/config.py +101 -0
- lovarch_cli/config_store.py +111 -0
- lovarch_cli/credits/__init__.py +35 -0
- lovarch_cli/credits/base.py +84 -0
- lovarch_cli/credits/factory.py +36 -0
- lovarch_cli/credits/local.py +34 -0
- lovarch_cli/credits/lovarch.py +56 -0
- lovarch_cli/i18n/__init__.py +27 -0
- lovarch_cli/i18n/loader.py +121 -0
- lovarch_cli/i18n/translations/en.json +168 -0
- lovarch_cli/i18n/translations/es.json +168 -0
- lovarch_cli/i18n/translations/it.json +168 -0
- lovarch_cli/i18n/translations/pt.json +168 -0
- lovarch_cli/mcp/__init__.py +9 -0
- lovarch_cli/mcp/server.py +199 -0
- lovarch_cli/mcp/tools.py +372 -0
- lovarch_cli/sample_downloader.py +255 -0
- lovarch_cli/squad/README.md +206 -0
- lovarch_cli/squad/agents/auditor-input.md +353 -0
- lovarch_cli/squad/agents/bim-engineer.md +404 -0
- lovarch_cli/squad/agents/briefing-architect.md +249 -0
- lovarch_cli/squad/agents/cad-engineer.md +278 -0
- lovarch_cli/squad/agents/capitolato-writer.md +256 -0
- lovarch_cli/squad/agents/computo-engineer.md +258 -0
- lovarch_cli/squad/agents/concept-designer.md +399 -0
- lovarch_cli/squad/agents/contratto-architect.md +243 -0
- lovarch_cli/squad/agents/deliverable-builder.md +253 -0
- lovarch_cli/squad/agents/energy-prelim.md +388 -0
- lovarch_cli/squad/agents/pratiche-it.md +251 -0
- lovarch_cli/squad/agents/progetto-chief.md +768 -0
- lovarch_cli/squad/agents/quality-dati.md +409 -0
- lovarch_cli/squad/agents/quality-misure.md +418 -0
- lovarch_cli/squad/agents/quality-normativa.md +417 -0
- lovarch_cli/squad/agents/quality-output.md +436 -0
- lovarch_cli/squad/agents/regolatorio-it.md +278 -0
- lovarch_cli/squad/checklists/handoff-quality-gate.md +232 -0
- lovarch_cli/squad/checklists/quality-dati-checklist.md +134 -0
- lovarch_cli/squad/checklists/quality-misure-checklist.md +139 -0
- lovarch_cli/squad/checklists/quality-normativa-checklist.md +121 -0
- lovarch_cli/squad/checklists/quality-output-checklist.md +116 -0
- lovarch_cli/squad/config.yaml +408 -0
- lovarch_cli/squad/data/CHANGELOG.md +272 -0
- lovarch_cli/squad/data/agents-prd.md +428 -0
- lovarch_cli/squad/data/architettura-progetto-rules.md +328 -0
- lovarch_cli/squad/data/handoff-card-template.md +231 -0
- lovarch_cli/squad/data/mocks/catasto-visura.json +72 -0
- lovarch_cli/squad/data/mocks/firma-envelope.json +43 -0
- lovarch_cli/squad/data/prezzario-lombardia-sample.json +312 -0
- lovarch_cli/squad/scripts/api_clients.py +206 -0
- lovarch_cli/squad/scripts/architect_profile.py +276 -0
- lovarch_cli/squad/scripts/deliverable_generators.py +844 -0
- lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +369 -0
- lovarch_cli/squad/scripts/generate_chianti_dxf.py +368 -0
- lovarch_cli/squad/scripts/generate_chianti_images.py +223 -0
- lovarch_cli/squad/scripts/generate_real_sample_images.py +189 -0
- lovarch_cli/squad/scripts/generate_sample_assets.py +382 -0
- lovarch_cli/squad/scripts/lovarch_client.py +1046 -0
- lovarch_cli/squad/scripts/pipeline_runner.py +2095 -0
- lovarch_cli/squad/scripts/render_dxf_to_png.py +57 -0
- lovarch_cli/squad/scripts/run_palestra_demo.sh +277 -0
- lovarch_cli/squad/scripts/simulate_squad_execution.py +515 -0
- lovarch_cli/squad/scripts/validate-squad.py +383 -0
- lovarch_cli/squad/tasks/audit-input.md +146 -0
- lovarch_cli/squad/tasks/compute-metric.md +105 -0
- lovarch_cli/squad/tasks/consolidate-dossier.md +187 -0
- lovarch_cli/squad/tasks/generate-cad-plan.md +120 -0
- lovarch_cli/squad/tasks/generate-ifc-model.md +108 -0
- lovarch_cli/squad/tasks/write-capitolato.md +100 -0
- lovarch_cli/squad/templates/asseverazione-tecnica.md +126 -0
- lovarch_cli/squad/templates/capitolato-uni-11337.md +235 -0
- lovarch_cli/squad/templates/cila-comune-milano.md +177 -0
- lovarch_cli/squad/templates/contratto-cnappc.md +220 -0
- lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +218 -0
- lovarch_cli/squad_loader.py +114 -0
- lovarch_cli/verify/__init__.py +15 -0
- lovarch_cli/verify/contratto.py +110 -0
- lovarch_cli/verify/dossier.py +97 -0
- lovarch_cli/verify/misure.py +83 -0
- lovarch_cli/verify/normativa.py +178 -0
- lovarch_cli/version.py +13 -0
- lovarch_cli/workflows/__init__.py +9 -0
- lovarch_cli/workflows/platform.py +212 -0
- lovarch_cli-0.2.1.dist-info/METADATA +232 -0
- lovarch_cli-0.2.1.dist-info/RECORD +122 -0
- lovarch_cli-0.2.1.dist-info/WHEEL +4 -0
- lovarch_cli-0.2.1.dist-info/entry_points.txt +3 -0
- lovarch_cli-0.2.1.dist-info/licenses/LICENSE +38 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""
|
|
2
|
+
validate-squad.py · AIOS Quality Gate Validator
|
|
3
|
+
|
|
4
|
+
Runs squad-checklist v3.1 (Tier 1-4) against squad architettura-progetto
|
|
5
|
+
and outputs a quality score (target: 10/10).
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python3 validate-squad.py [--verbose]
|
|
9
|
+
|
|
10
|
+
Exit codes:
|
|
11
|
+
0 = PASS (score ≥7.0)
|
|
12
|
+
1 = FAIL (score <7.0)
|
|
13
|
+
2 = ERROR (validation crashed)
|
|
14
|
+
"""
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Tuple
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
SQUAD_DIR = Path(__file__).parent.parent
|
|
23
|
+
VERBOSE = "--verbose" in sys.argv
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ============================================================================
|
|
27
|
+
# TIER 1 · STRUCTURE (BLOCKING)
|
|
28
|
+
# ============================================================================
|
|
29
|
+
|
|
30
|
+
def check_tier_1() -> Tuple[float, List[Dict]]:
|
|
31
|
+
"""Returns (score 0-10, list of check results)"""
|
|
32
|
+
checks = []
|
|
33
|
+
|
|
34
|
+
# 1.1 config.yaml exists + parseable
|
|
35
|
+
config_path = SQUAD_DIR / "config.yaml"
|
|
36
|
+
config_exists = config_path.exists()
|
|
37
|
+
checks.append({"id": "1.1", "name": "config.yaml exists", "pass": config_exists, "severity": "BLOCKING"})
|
|
38
|
+
|
|
39
|
+
config_text = config_path.read_text() if config_exists else ""
|
|
40
|
+
|
|
41
|
+
# 1.1 Required fields
|
|
42
|
+
required_fields = ["name:", "version:", "description:", "entry_agent:"]
|
|
43
|
+
for field in required_fields:
|
|
44
|
+
present = field in config_text
|
|
45
|
+
checks.append({
|
|
46
|
+
"id": f"1.1.{field}",
|
|
47
|
+
"name": f"config field {field}",
|
|
48
|
+
"pass": present,
|
|
49
|
+
"severity": "BLOCKING",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
# 1.1 author
|
|
53
|
+
author_present = "author:" in config_text
|
|
54
|
+
checks.append({"id": "1.1.author", "name": "config has author", "pass": author_present, "severity": "BLOCKING"})
|
|
55
|
+
|
|
56
|
+
# 1.1 slashPrefix
|
|
57
|
+
slash_present = "slashPrefix:" in config_text
|
|
58
|
+
checks.append({"id": "1.1.slashPrefix", "name": "config has slashPrefix", "pass": slash_present, "severity": "BLOCKING"})
|
|
59
|
+
|
|
60
|
+
# 1.4 Directory structure
|
|
61
|
+
dirs = ["agents", "tasks", "checklists", "data", "templates", "workflows", "scripts"]
|
|
62
|
+
for d in dirs:
|
|
63
|
+
d_path = SQUAD_DIR / d
|
|
64
|
+
exists = d_path.exists() and d_path.is_dir()
|
|
65
|
+
checks.append({"id": f"1.4.{d}", "name": f"dir {d}/", "pass": exists, "severity": "BLOCKING"})
|
|
66
|
+
|
|
67
|
+
# 1.4 Min agents count (>=1)
|
|
68
|
+
agents_dir = SQUAD_DIR / "agents"
|
|
69
|
+
agents_count = len(list(agents_dir.glob("*.md"))) if agents_dir.exists() else 0
|
|
70
|
+
checks.append({
|
|
71
|
+
"id": "1.4.agents_count",
|
|
72
|
+
"name": f"agents count >=1 (actual: {agents_count})",
|
|
73
|
+
"pass": agents_count >= 1,
|
|
74
|
+
"severity": "BLOCKING",
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
# 1.4 Min tasks count (>=1 per AIOS standard)
|
|
78
|
+
tasks_dir = SQUAD_DIR / "tasks"
|
|
79
|
+
tasks_count = len(list(tasks_dir.glob("*.md"))) if tasks_dir.exists() else 0
|
|
80
|
+
checks.append({
|
|
81
|
+
"id": "1.4.tasks_count",
|
|
82
|
+
"name": f"tasks count >=1 (actual: {tasks_count})",
|
|
83
|
+
"pass": tasks_count >= 1,
|
|
84
|
+
"severity": "BLOCKING",
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
# 1.5 Cross-references
|
|
88
|
+
central_doc = SQUAD_DIR / "data" / "architettura-progetto-rules.md"
|
|
89
|
+
central_exists = central_doc.exists()
|
|
90
|
+
checks.append({"id": "1.5.central_doc", "name": "central document exists", "pass": central_exists, "severity": "BLOCKING"})
|
|
91
|
+
|
|
92
|
+
changelog = SQUAD_DIR / "data" / "CHANGELOG.md"
|
|
93
|
+
changelog_exists = changelog.exists()
|
|
94
|
+
checks.append({"id": "1.5.changelog", "name": "CHANGELOG.md exists", "pass": changelog_exists, "severity": "BLOCKING"})
|
|
95
|
+
|
|
96
|
+
handoff_card = SQUAD_DIR / "data" / "handoff-card-template.md"
|
|
97
|
+
handoff_card_exists = handoff_card.exists()
|
|
98
|
+
checks.append({"id": "1.5.handoff_card", "name": "handoff-card-template exists", "pass": handoff_card_exists, "severity": "BLOCKING"})
|
|
99
|
+
|
|
100
|
+
handoff_qa = SQUAD_DIR / "checklists" / "handoff-quality-gate.md"
|
|
101
|
+
handoff_qa_exists = handoff_qa.exists()
|
|
102
|
+
checks.append({"id": "1.5.handoff_qa", "name": "handoff-quality-gate exists", "pass": handoff_qa_exists, "severity": "BLOCKING"})
|
|
103
|
+
|
|
104
|
+
# 1.6 Security scan (no hardcoded secrets in YAML)
|
|
105
|
+
secrets_pattern = re.compile(r"(api[_-]?key|secret|password)\s*[:=]\s*['\"][^'\"]{20,}", re.IGNORECASE)
|
|
106
|
+
has_secrets = bool(secrets_pattern.search(config_text))
|
|
107
|
+
checks.append({"id": "1.6.no_secrets", "name": "no hardcoded secrets", "pass": not has_secrets, "severity": "BLOCKING"})
|
|
108
|
+
|
|
109
|
+
# Score
|
|
110
|
+
total = len(checks)
|
|
111
|
+
passed = sum(1 for c in checks if c["pass"])
|
|
112
|
+
score = (passed / total) * 10
|
|
113
|
+
return score, checks
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ============================================================================
|
|
117
|
+
# TIER 2 · COVERAGE (BLOCKING)
|
|
118
|
+
# ============================================================================
|
|
119
|
+
|
|
120
|
+
def check_tier_2() -> Tuple[float, List[Dict]]:
|
|
121
|
+
checks = []
|
|
122
|
+
|
|
123
|
+
# Tier 0 orchestrator exists
|
|
124
|
+
chief_exists = (SQUAD_DIR / "agents" / "progetto-chief.md").exists()
|
|
125
|
+
checks.append({"id": "2.1.chief", "name": "Tier 0 orchestrator exists", "pass": chief_exists, "severity": "BLOCKING"})
|
|
126
|
+
|
|
127
|
+
# Auditor input
|
|
128
|
+
auditor_exists = (SQUAD_DIR / "agents" / "auditor-input.md").exists()
|
|
129
|
+
checks.append({"id": "2.2.auditor", "name": "auditor-input exists", "pass": auditor_exists, "severity": "BLOCKING"})
|
|
130
|
+
|
|
131
|
+
# Tier 1 specialists ≥5
|
|
132
|
+
tier1_agents = [
|
|
133
|
+
"briefing-architect", "regolatorio-it", "concept-designer", "cad-engineer",
|
|
134
|
+
"bim-engineer", "computo-engineer", "capitolato-writer", "pratiche-it",
|
|
135
|
+
"contratto-architect", "energy-prelim", "deliverable-builder",
|
|
136
|
+
]
|
|
137
|
+
tier1_present = sum(1 for a in tier1_agents if (SQUAD_DIR / "agents" / f"{a}.md").exists())
|
|
138
|
+
checks.append({
|
|
139
|
+
"id": "2.3.tier1_count",
|
|
140
|
+
"name": f"Tier 1 specialists ≥5 (actual: {tier1_present})",
|
|
141
|
+
"pass": tier1_present >= 5,
|
|
142
|
+
"severity": "BLOCKING",
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# Tier 2 QA agents
|
|
146
|
+
qa_agents = ["quality-misure", "quality-normativa", "quality-dati", "quality-output"]
|
|
147
|
+
qa_present = sum(1 for a in qa_agents if (SQUAD_DIR / "agents" / f"{a}.md").exists())
|
|
148
|
+
checks.append({
|
|
149
|
+
"id": "2.4.qa_count",
|
|
150
|
+
"name": f"Tier 2 QA agents ≥4 (actual: {qa_present})",
|
|
151
|
+
"pass": qa_present >= 4,
|
|
152
|
+
"severity": "BLOCKING",
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
# Tasks ≥3 (atomic)
|
|
156
|
+
tasks = list((SQUAD_DIR / "tasks").glob("*.md")) if (SQUAD_DIR / "tasks").exists() else []
|
|
157
|
+
checks.append({
|
|
158
|
+
"id": "2.5.tasks_count",
|
|
159
|
+
"name": f"Tasks ≥3 (actual: {len(tasks)})",
|
|
160
|
+
"pass": len(tasks) >= 3,
|
|
161
|
+
"severity": "BLOCKING",
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
# Workflows ≥1
|
|
165
|
+
workflows = list((SQUAD_DIR / "workflows").glob("*.yaml")) if (SQUAD_DIR / "workflows").exists() else []
|
|
166
|
+
checks.append({
|
|
167
|
+
"id": "2.6.workflows_count",
|
|
168
|
+
"name": f"Workflows ≥1 (actual: {len(workflows)})",
|
|
169
|
+
"pass": len(workflows) >= 1,
|
|
170
|
+
"severity": "BLOCKING",
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
# Templates ≥3
|
|
174
|
+
templates = list((SQUAD_DIR / "templates").glob("*.md")) if (SQUAD_DIR / "templates").exists() else []
|
|
175
|
+
checks.append({
|
|
176
|
+
"id": "2.7.templates_count",
|
|
177
|
+
"name": f"Templates ≥3 (actual: {len(templates)})",
|
|
178
|
+
"pass": len(templates) >= 3,
|
|
179
|
+
"severity": "BLOCKING",
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
# Checklists ≥4 (1 handoff + 4 QA)
|
|
183
|
+
checklists = list((SQUAD_DIR / "checklists").glob("*.md")) if (SQUAD_DIR / "checklists").exists() else []
|
|
184
|
+
checks.append({
|
|
185
|
+
"id": "2.8.checklists_count",
|
|
186
|
+
"name": f"Checklists ≥5 (actual: {len(checklists)})",
|
|
187
|
+
"pass": len(checklists) >= 5,
|
|
188
|
+
"severity": "BLOCKING",
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
total = len(checks)
|
|
192
|
+
passed = sum(1 for c in checks if c["pass"])
|
|
193
|
+
score = (passed / total) * 10
|
|
194
|
+
return score, checks
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ============================================================================
|
|
198
|
+
# TIER 3 · QUALITY (THRESHOLD 7.0)
|
|
199
|
+
# ============================================================================
|
|
200
|
+
|
|
201
|
+
REQUIRED_AGENT_SECTIONS = [
|
|
202
|
+
"ACTIVATION-NOTICE",
|
|
203
|
+
"IDE-FILE-RESOLUTION",
|
|
204
|
+
"REQUEST-RESOLUTION",
|
|
205
|
+
"activation-instructions",
|
|
206
|
+
"command_loader",
|
|
207
|
+
"agent:",
|
|
208
|
+
"persona:",
|
|
209
|
+
"core_principles",
|
|
210
|
+
"voice_dna:",
|
|
211
|
+
"thinking_dna:",
|
|
212
|
+
"handoff_to:",
|
|
213
|
+
"output_examples:",
|
|
214
|
+
"anti_patterns:",
|
|
215
|
+
"completion_criteria:",
|
|
216
|
+
"smoke_tests:",
|
|
217
|
+
"integration:",
|
|
218
|
+
"greeting:",
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def check_agent_quality(agent_path: Path) -> Tuple[float, List[Dict]]:
|
|
223
|
+
"""Per-agent AIOS 6-level structure check."""
|
|
224
|
+
if not agent_path.exists():
|
|
225
|
+
return 0, [{"id": "missing", "name": f"{agent_path.name} missing", "pass": False, "severity": "BLOCKING"}]
|
|
226
|
+
|
|
227
|
+
content = agent_path.read_text()
|
|
228
|
+
checks = []
|
|
229
|
+
|
|
230
|
+
for section in REQUIRED_AGENT_SECTIONS:
|
|
231
|
+
present = section in content
|
|
232
|
+
checks.append({
|
|
233
|
+
"id": f"agent.{section}",
|
|
234
|
+
"name": f"{agent_path.name} has {section}",
|
|
235
|
+
"pass": present,
|
|
236
|
+
"severity": "QUALITY",
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
# Additional quality checks
|
|
240
|
+
has_smoke_3 = content.count("test_") >= 3
|
|
241
|
+
checks.append({"id": "agent.smoke_tests_3", "name": f"{agent_path.name} 3 smoke tests", "pass": has_smoke_3, "severity": "QUALITY"})
|
|
242
|
+
|
|
243
|
+
has_examples_3 = len(re.findall(r"- input:", content)) >= 3
|
|
244
|
+
checks.append({"id": "agent.examples_3", "name": f"{agent_path.name} 3 output examples", "pass": has_examples_3, "severity": "QUALITY"})
|
|
245
|
+
|
|
246
|
+
has_source_traces = "[SOURCE:" in content or "[Deming" in content or "[Juran" in content or "[English" in content or "[Dodds" in content or "[Schumacher" in content or "[Baldwin" in content or "[Mazria" in content or "signature]" in content
|
|
247
|
+
checks.append({"id": "agent.source_traces", "name": f"{agent_path.name} has [SOURCE:] traces", "pass": has_source_traces, "severity": "QUALITY"})
|
|
248
|
+
|
|
249
|
+
total = len(checks)
|
|
250
|
+
passed = sum(1 for c in checks if c["pass"])
|
|
251
|
+
score = (passed / total) * 10
|
|
252
|
+
return score, checks
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def check_tier_3() -> Tuple[float, List[Dict]]:
|
|
256
|
+
agents = list((SQUAD_DIR / "agents").glob("*.md"))
|
|
257
|
+
all_checks = []
|
|
258
|
+
scores = []
|
|
259
|
+
|
|
260
|
+
for agent_path in agents:
|
|
261
|
+
score, checks = check_agent_quality(agent_path)
|
|
262
|
+
scores.append(score)
|
|
263
|
+
all_checks.extend(checks)
|
|
264
|
+
|
|
265
|
+
avg_score = sum(scores) / len(scores) if scores else 0
|
|
266
|
+
return avg_score, all_checks
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ============================================================================
|
|
270
|
+
# TIER 4 · CONTEXTUAL (squad type)
|
|
271
|
+
# ============================================================================
|
|
272
|
+
|
|
273
|
+
def check_tier_4() -> Tuple[float, List[Dict]]:
|
|
274
|
+
"""Detect squad type · architettura-progetto = Hybrid (process + heuristics)"""
|
|
275
|
+
checks = []
|
|
276
|
+
config_text = (SQUAD_DIR / "config.yaml").read_text() if (SQUAD_DIR / "config.yaml").exists() else ""
|
|
277
|
+
|
|
278
|
+
has_hub_spoke = "hub_and_spoke" in config_text
|
|
279
|
+
checks.append({"id": "4.1.hub_spoke", "name": "Hub-and-spoke topology", "pass": has_hub_spoke, "severity": "QUALITY"})
|
|
280
|
+
|
|
281
|
+
has_executor_types = "executor_types:" in config_text
|
|
282
|
+
checks.append({"id": "4.2.executor_types", "name": "Executor types defined", "pass": has_executor_types, "severity": "QUALITY"})
|
|
283
|
+
|
|
284
|
+
has_pattern_lib = "pattern_library:" in config_text
|
|
285
|
+
checks.append({"id": "4.3.pattern_library", "name": "Pattern library defined", "pass": has_pattern_lib, "severity": "QUALITY"})
|
|
286
|
+
|
|
287
|
+
has_quality_gates = "quality_gates:" in config_text
|
|
288
|
+
checks.append({"id": "4.4.quality_gates", "name": "Quality gates referenced", "pass": has_quality_gates, "severity": "QUALITY"})
|
|
289
|
+
|
|
290
|
+
# Mind clones traceable
|
|
291
|
+
mind_clones = ["quality-misure", "quality-normativa", "quality-dati", "quality-output", "concept-designer", "bim-engineer", "energy-prelim"]
|
|
292
|
+
clones_with_source = 0
|
|
293
|
+
for c in mind_clones:
|
|
294
|
+
cpath = SQUAD_DIR / "agents" / f"{c}.md"
|
|
295
|
+
if cpath.exists() and "based_on:" in cpath.read_text():
|
|
296
|
+
clones_with_source += 1
|
|
297
|
+
checks.append({
|
|
298
|
+
"id": "4.5.mind_clones",
|
|
299
|
+
"name": f"Mind clones documented ({clones_with_source}/7)",
|
|
300
|
+
"pass": clones_with_source >= 7,
|
|
301
|
+
"severity": "QUALITY",
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
total = len(checks)
|
|
305
|
+
passed = sum(1 for c in checks if c["pass"])
|
|
306
|
+
score = (passed / total) * 10
|
|
307
|
+
return score, checks
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ============================================================================
|
|
311
|
+
# RUNNER
|
|
312
|
+
# ============================================================================
|
|
313
|
+
|
|
314
|
+
def main():
|
|
315
|
+
print("\n" + "=" * 70)
|
|
316
|
+
print(" Squad Validation · architettura-progetto")
|
|
317
|
+
print(" Applying squad-checklist v3.1 · AIOS Standard")
|
|
318
|
+
print("=" * 70 + "\n")
|
|
319
|
+
|
|
320
|
+
t1_score, t1_checks = check_tier_1()
|
|
321
|
+
t2_score, t2_checks = check_tier_2()
|
|
322
|
+
t3_score, t3_checks = check_tier_3()
|
|
323
|
+
t4_score, t4_checks = check_tier_4()
|
|
324
|
+
|
|
325
|
+
# Final score (weighted: T3 × 0.5 + T1 × 0.2 + T2 × 0.2 + T4 × 0.1)
|
|
326
|
+
final = (t3_score * 0.5) + (t1_score * 0.2) + (t2_score * 0.2) + (t4_score * 0.1)
|
|
327
|
+
|
|
328
|
+
print(f"Tier 1 · Structure: {t1_score:5.2f}/10 ({sum(1 for c in t1_checks if c['pass'])}/{len(t1_checks)} checks)")
|
|
329
|
+
print(f"Tier 2 · Coverage: {t2_score:5.2f}/10 ({sum(1 for c in t2_checks if c['pass'])}/{len(t2_checks)} checks)")
|
|
330
|
+
print(f"Tier 3 · Quality: {t3_score:5.2f}/10 (per-agent average · {len(list((SQUAD_DIR/'agents').glob('*.md')))} agents)")
|
|
331
|
+
print(f"Tier 4 · Contextual: {t4_score:5.2f}/10 ({sum(1 for c in t4_checks if c['pass'])}/{len(t4_checks)} checks)")
|
|
332
|
+
print(f"")
|
|
333
|
+
print(f" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
334
|
+
print(f" FINAL SCORE: {final:5.2f}/10")
|
|
335
|
+
print(f" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
336
|
+
print(f"")
|
|
337
|
+
|
|
338
|
+
if final >= 9.0:
|
|
339
|
+
print(" Verdict: EXCELLENCE 🏆")
|
|
340
|
+
elif final >= 7.0:
|
|
341
|
+
print(" Verdict: PASS ✓")
|
|
342
|
+
else:
|
|
343
|
+
print(" Verdict: FAIL ✗")
|
|
344
|
+
print("")
|
|
345
|
+
|
|
346
|
+
if VERBOSE:
|
|
347
|
+
print("\n--- Tier 1 · Structure (BLOCKING) ---")
|
|
348
|
+
for c in t1_checks:
|
|
349
|
+
mark = "✓" if c["pass"] else "✗"
|
|
350
|
+
print(f" {mark} [{c['id']}] {c['name']}")
|
|
351
|
+
|
|
352
|
+
print("\n--- Tier 2 · Coverage (BLOCKING) ---")
|
|
353
|
+
for c in t2_checks:
|
|
354
|
+
mark = "✓" if c["pass"] else "✗"
|
|
355
|
+
print(f" {mark} [{c['id']}] {c['name']}")
|
|
356
|
+
|
|
357
|
+
# Tier 3 · group by agent
|
|
358
|
+
print("\n--- Tier 3 · Quality (per agent) ---")
|
|
359
|
+
agents = list((SQUAD_DIR / "agents").glob("*.md"))
|
|
360
|
+
for agent_path in agents:
|
|
361
|
+
score, checks = check_agent_quality(agent_path)
|
|
362
|
+
print(f" {agent_path.stem}: {score:.1f}/10 ({sum(1 for c in checks if c['pass'])}/{len(checks)})")
|
|
363
|
+
|
|
364
|
+
print("\n--- Tier 4 · Contextual ---")
|
|
365
|
+
for c in t4_checks:
|
|
366
|
+
mark = "✓" if c["pass"] else "✗"
|
|
367
|
+
print(f" {mark} [{c['id']}] {c['name']}")
|
|
368
|
+
|
|
369
|
+
# Failed only
|
|
370
|
+
all_failed = [c for c in (t1_checks + t2_checks + t4_checks) if not c["pass"]]
|
|
371
|
+
if all_failed:
|
|
372
|
+
print(f"\n--- {len(all_failed)} FAILED CHECKS ---")
|
|
373
|
+
for c in all_failed:
|
|
374
|
+
print(f" ✗ [{c['id']}] {c['name']}")
|
|
375
|
+
|
|
376
|
+
if final >= 7.0:
|
|
377
|
+
sys.exit(0)
|
|
378
|
+
else:
|
|
379
|
+
sys.exit(1)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
if __name__ == "__main__":
|
|
383
|
+
main()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Task: audit-input
|
|
2
|
+
|
|
3
|
+
> **Pattern:** AP-TP-001 (Atomic Task Anatomy · 8 mandatory fields)
|
|
4
|
+
> **Executor:** @auditor-input (functional · Tier 0)
|
|
5
|
+
> **Squad:** architettura-progetto
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## task_name
|
|
10
|
+
Audit input completeness · 18-item checklist gate
|
|
11
|
+
|
|
12
|
+
## status
|
|
13
|
+
ACTIVE · stable · v1.0
|
|
14
|
+
|
|
15
|
+
## responsible_executor
|
|
16
|
+
- **agent**: @auditor-input
|
|
17
|
+
- **executor_type**: AP-EP-002 (Agent Executor)
|
|
18
|
+
- **fallback**: @progetto-chief escalation if agent timeout
|
|
19
|
+
|
|
20
|
+
## execution_type
|
|
21
|
+
**Synchronous · Pre-flight gate** · Blocks workflow if FAIL
|
|
22
|
+
|
|
23
|
+
## input
|
|
24
|
+
```yaml
|
|
25
|
+
required:
|
|
26
|
+
brief_path: "Path to briefing-cliente.md"
|
|
27
|
+
dwg_path: "Path to stato-attuale.dxf"
|
|
28
|
+
photos_dir: "Directory with stato attuale photos"
|
|
29
|
+
cliente_data:
|
|
30
|
+
nome: string
|
|
31
|
+
cognome: string
|
|
32
|
+
codice_fiscale: string # 16-char Italian CF
|
|
33
|
+
email: string
|
|
34
|
+
telefono: string
|
|
35
|
+
studio_data:
|
|
36
|
+
architetto: string
|
|
37
|
+
n_ordine: string
|
|
38
|
+
piva: string
|
|
39
|
+
pec: string
|
|
40
|
+
valore_opera: number # EUR
|
|
41
|
+
optional:
|
|
42
|
+
visura_path: "Catastal record PDF"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## output
|
|
46
|
+
```yaml
|
|
47
|
+
file: "input_validation.json"
|
|
48
|
+
schema:
|
|
49
|
+
validation_id: uuid
|
|
50
|
+
status: "PASS | FAIL"
|
|
51
|
+
missing: array[string] # item ids that failed
|
|
52
|
+
warnings: array[string]
|
|
53
|
+
extracted_data:
|
|
54
|
+
client_name: string
|
|
55
|
+
client_cf_primary: string
|
|
56
|
+
address: string
|
|
57
|
+
geocoded:
|
|
58
|
+
lat: number
|
|
59
|
+
lon: number
|
|
60
|
+
comune: string
|
|
61
|
+
postcode: string
|
|
62
|
+
project_value: number
|
|
63
|
+
studio: object
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## action_items
|
|
67
|
+
1. **A1** Read briefing-cliente.md · verify char count ≥500
|
|
68
|
+
2. **A2** Verify briefing has ≥3 of 12 sezioni UNI 11337
|
|
69
|
+
3. **A3** Extract budget numerico (regex /€\s?[\d,.]+/)
|
|
70
|
+
4. **A4** Extract timeline indicators
|
|
71
|
+
5. **A5** Verify briefing has cliente expectations section
|
|
72
|
+
6. **B1** Run `ezdxf.readfile(dwg)` · entities count > 0
|
|
73
|
+
7. **B2** Iterate photos · verify ≥3 JPG/PNG · resolution ≥800x600
|
|
74
|
+
8. **B3** Check visura PDF if present
|
|
75
|
+
9. **C1** Verify cliente nome+cognome non-empty
|
|
76
|
+
10. **C2-C3** Verify CF format /^[A-Z0-9]{16}$/ · Italian checksum algorithm
|
|
77
|
+
11. **C4** Verify email regex
|
|
78
|
+
12. **C5** Mapbox API call · geocode address · features.length > 0
|
|
79
|
+
13. **D1** Verify architetto nome
|
|
80
|
+
14. **D2** Verify n. Ordine (digit string)
|
|
81
|
+
15. **D3** Verify P.IVA studio (11-digit + checksum)
|
|
82
|
+
16. **D4** Verify PEC format
|
|
83
|
+
17. **E1** Verify valore_opera numeric > 0
|
|
84
|
+
18. **Final** Build extracted_data + verdict
|
|
85
|
+
|
|
86
|
+
## acceptance_criteria
|
|
87
|
+
- [ ] All 18 items checked (no skipping)
|
|
88
|
+
- [ ] If 7/7 critici (A1, B1, B2, C2-C3, C5, D3, E1) PASS → status=PASS
|
|
89
|
+
- [ ] If ANY critico FAIL → status=FAIL · workflow halts
|
|
90
|
+
- [ ] JSON output valid against schema
|
|
91
|
+
- [ ] If PASS · extracted_data populated for downstream
|
|
92
|
+
- [ ] If FAIL · missing[] lists exact item_ids
|
|
93
|
+
- [ ] Verdict announcement returned to @progetto-chief
|
|
94
|
+
|
|
95
|
+
## dependencies
|
|
96
|
+
- **APIs:**
|
|
97
|
+
- Mapbox Geocoding API (item C5 critical)
|
|
98
|
+
- Italian CF checksum library (item C2-C3)
|
|
99
|
+
- **Files:**
|
|
100
|
+
- All input files (brief, dwg, photos, etc.)
|
|
101
|
+
- **Agents:**
|
|
102
|
+
- @progetto-chief (invoker · returns to)
|
|
103
|
+
|
|
104
|
+
## templates
|
|
105
|
+
- `~/projects/{slug}/01-input/` (expected input directory structure)
|
|
106
|
+
- input_validation.json schema (above)
|
|
107
|
+
|
|
108
|
+
## quality_gate
|
|
109
|
+
- **Gate:** QG-AP-1.1 (Input Validation Gate)
|
|
110
|
+
- **Threshold:** 7/7 CRITICI must PASS (A1, B1, B2, C2-C3, C5, D3, E1)
|
|
111
|
+
- **Reviewer:** @progetto-chief on receipt
|
|
112
|
+
|
|
113
|
+
## handoff
|
|
114
|
+
- **From:** @progetto-chief (outbound card)
|
|
115
|
+
- **To:** @progetto-chief (inbound card with validation_id + verdict)
|
|
116
|
+
- **Required announcement:** "Ritorno al @progetto-chief. Audit completato — verdict: {PASS|FAIL}."
|
|
117
|
+
|
|
118
|
+
## veto_conditions
|
|
119
|
+
- Briefing < 500 chars → FAIL (A1)
|
|
120
|
+
- DWG corrupted (ezdxf raises) → FAIL (B1)
|
|
121
|
+
- Photos < 3 → FAIL (B2)
|
|
122
|
+
- Address not geocodable → FAIL (C5)
|
|
123
|
+
- Invalid CF → FAIL (C2-C3)
|
|
124
|
+
- Invalid P.IVA → FAIL (D3)
|
|
125
|
+
- Valore opera ≤0 → FAIL (E1)
|
|
126
|
+
|
|
127
|
+
## estimated_time
|
|
128
|
+
**60-90 seconds** (Mapbox API call dominant)
|
|
129
|
+
|
|
130
|
+
## output_example
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"validation_id": "v_a7f4b2",
|
|
134
|
+
"status": "PASS",
|
|
135
|
+
"missing": [],
|
|
136
|
+
"warnings": [],
|
|
137
|
+
"extracted_data": {
|
|
138
|
+
"client_name": "Marco Rossini & Giulia Bianchi",
|
|
139
|
+
"client_cf_primary": "RSSMRC83A15F205X",
|
|
140
|
+
"address": "Via Fiori Chiari 17, 20121 Milano",
|
|
141
|
+
"geocoded": {"lat": 45.471823, "lon": 9.184828, "comune": "Milano", "postcode": "20121"},
|
|
142
|
+
"project_value": 180000,
|
|
143
|
+
"studio": {"nome": "Pablo Ruan", "ordine_n": "XXXX", "piva": "01234567890"}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Task: compute-metric
|
|
2
|
+
|
|
3
|
+
> **Pattern:** AP-TP-001
|
|
4
|
+
> **Executor:** @computo-engineer (functional · Tier 1 · critical: dati_zero_tolerance)
|
|
5
|
+
> **Squad:** architettura-progetto
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## task_name
|
|
10
|
+
Compute metric estimative · Prezzario Lombardia 2025 · IVA 10%
|
|
11
|
+
|
|
12
|
+
## status
|
|
13
|
+
ACTIVE · stable · v1.0
|
|
14
|
+
|
|
15
|
+
## responsible_executor
|
|
16
|
+
- **agent**: @computo-engineer
|
|
17
|
+
- **executor_type**: AP-EP-002 (Agent + Python worker)
|
|
18
|
+
- **worker**: xlsxwriter, openpyxl, pdfplumber
|
|
19
|
+
|
|
20
|
+
## execution_type
|
|
21
|
+
**Synchronous after BIM** · Output feeds @capitolato-writer + @quality-dati
|
|
22
|
+
|
|
23
|
+
## input
|
|
24
|
+
```yaml
|
|
25
|
+
required:
|
|
26
|
+
quantitativi_json: "From @bim-engineer"
|
|
27
|
+
prezzario_path: "data/prezzario-lombardia-sample.json"
|
|
28
|
+
optional:
|
|
29
|
+
dei_plus_subscription: bool # fallback per voci mancanti
|
|
30
|
+
ec3_for_epd: bool # for EPD tracking
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## output
|
|
34
|
+
```yaml
|
|
35
|
+
files:
|
|
36
|
+
- "05-impresa/computo-metrico.xlsx"
|
|
37
|
+
- "05-impresa/computo-metrico.pdf"
|
|
38
|
+
- "05-impresa/quadro-economico.pdf"
|
|
39
|
+
- "05-impresa/lista-materiali-EPDs.xlsx"
|
|
40
|
+
metrics:
|
|
41
|
+
voci_count: number
|
|
42
|
+
totale_lavori_eur: number
|
|
43
|
+
iva_eur: number
|
|
44
|
+
totale_iva_inclusa: number
|
|
45
|
+
cam_compliance_percent: number
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## action_items
|
|
49
|
+
1. Read quantitativi.json (volumi muri, aree pavimenti, count, etc.)
|
|
50
|
+
2. Match each quantity to Prezzario Lombardia voce (semantic + code)
|
|
51
|
+
3. For unmatched: query DEI fallback OR mark [VERIFY-CUSTOM]
|
|
52
|
+
4. Compute Q × prezzo_unitario per voce
|
|
53
|
+
5. Aggregate per categoria DEI (demolizioni, murature, impianti, etc.)
|
|
54
|
+
6. Apply IVA 10% (ristrutturazione · DPR 633/72)
|
|
55
|
+
7. Build quadro economico (lavori + onorari + oneri + IVA + imprevisti)
|
|
56
|
+
8. Track CAM 2025 compliance per voce (target ≥80%)
|
|
57
|
+
9. Cross-reference EC3 for EPDs in materials list
|
|
58
|
+
10. Generate xlsx with formulas (SUM, IVA calc)
|
|
59
|
+
11. Generate PDF version
|
|
60
|
+
12. Self-verify: sum quantitativi IFC = sum computo (diff <2%)
|
|
61
|
+
|
|
62
|
+
## acceptance_criteria
|
|
63
|
+
- [ ] ≥100 voci with codice Prezzario or [VERIFY-CUSTOM]
|
|
64
|
+
- [ ] Aggregazione per categoria DEI completa
|
|
65
|
+
- [ ] IVA 10% applied (NOT 22%)
|
|
66
|
+
- [ ] Quadro economico generato
|
|
67
|
+
- [ ] Cross-check IFC quantitativi · diff <2%
|
|
68
|
+
- [ ] CAM 2025 tracking ≥80% target
|
|
69
|
+
- [ ] xlsx with working formulas
|
|
70
|
+
- [ ] PDF readable
|
|
71
|
+
|
|
72
|
+
## dependencies
|
|
73
|
+
- **Libraries:**
|
|
74
|
+
- xlsxwriter (xlsx generation)
|
|
75
|
+
- openpyxl (template editing)
|
|
76
|
+
- pdfplumber (parse Prezzario PDF if needed)
|
|
77
|
+
- **APIs:**
|
|
78
|
+
- DEI PLUS (optional · fallback)
|
|
79
|
+
- EC3 Building Transparency (EPDs)
|
|
80
|
+
- Gemini 3.1 Pro (gemini-3.1-pro-preview) (semantic mapping descrizione → voce)
|
|
81
|
+
- **Data:**
|
|
82
|
+
- Prezzario Regione Lombardia 2025 cached JSON
|
|
83
|
+
- quantitativi.json (@bim-engineer)
|
|
84
|
+
|
|
85
|
+
## quality_gate
|
|
86
|
+
- **Gate:** QG-AP-1.4 (Cross-Doc Data Gate)
|
|
87
|
+
- **Reviewer:** @quality-dati
|
|
88
|
+
- **Threshold:** Cross-check IFC = computo · diff <2%
|
|
89
|
+
|
|
90
|
+
## handoff
|
|
91
|
+
- **From:** @progetto-chief
|
|
92
|
+
- **To:** @progetto-chief → @capitolato-writer (uses computo) + @quality-dati (verifies)
|
|
93
|
+
- **Required announcement:** "Ritorno al @progetto-chief. Computo · {n} voci · totale € {X}."
|
|
94
|
+
|
|
95
|
+
## veto_conditions
|
|
96
|
+
- Totale computo ≠ sum voci > 0.5% → halt
|
|
97
|
+
- IVA 22% applied → halt (errata · ristrutturazione = 10%)
|
|
98
|
+
- Voci senza prezzo → halt
|
|
99
|
+
- Diff vs IFC quantitativi >2% → halt + flag @quality-dati REJECT predicted
|
|
100
|
+
|
|
101
|
+
## estimated_time
|
|
102
|
+
**45-60 seconds**
|
|
103
|
+
|
|
104
|
+
## output_example
|
|
105
|
+
See `@computo-engineer.md` output_examples · 124 voci · €162,327 · CAM 87% · cross-check pass.
|