refactor: make ARNES external-repo based with ticket publish flow
This commit is contained in:
@@ -18,6 +18,11 @@ echo "── 1) Verificando estructura base ────────────
|
||||
required=(
|
||||
"AGENTS.md"
|
||||
"CHECKPOINTS.md"
|
||||
"README.md"
|
||||
"HOWTO.md"
|
||||
"TEMPLATE.md"
|
||||
"docs/repository-layout.md"
|
||||
"docs/scripts-reference.md"
|
||||
"harness/agents.matrix.yml"
|
||||
"harness/workflow.stages.yml"
|
||||
"harness/policies/governance.md"
|
||||
@@ -31,14 +36,24 @@ required=(
|
||||
"spec/product.md"
|
||||
"spec/tech.md"
|
||||
"spec/acceptance.md"
|
||||
"spec/bdd/README.md"
|
||||
"spec/bdd/features/README.md"
|
||||
"spec/sdd/README.md"
|
||||
"spec/sdd/components/README.md"
|
||||
"spec/sdd/decisions/README.md"
|
||||
"features/README.md"
|
||||
"project/README.md"
|
||||
"backlog/features.json"
|
||||
"work/current.md"
|
||||
"work/history.md"
|
||||
"work/runtime-status.json"
|
||||
"scripts/agent_status.py"
|
||||
"scripts/new_ticket.py"
|
||||
"scripts/publish_ticket.py"
|
||||
"scripts/install_into_repo.sh"
|
||||
"scripts/start.sh"
|
||||
"platforms/pi/README.md"
|
||||
"platforms/opencode/README.md"
|
||||
)
|
||||
|
||||
for f in "${required[@]}"; do
|
||||
@@ -50,6 +65,20 @@ for f in "${required[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "── 1.5) Validando git repo ───────────────────────────"
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
ok "Git repo detectado"
|
||||
if git remote | grep -q .; then
|
||||
ok "Git remote configurado"
|
||||
else
|
||||
warn "Sin git remote configurado (publish requerirá remote)"
|
||||
fi
|
||||
else
|
||||
fail "Este proyecto debe vivir dentro de un git repo"
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "── 2) Validando backlog + gates ───────────────────────"
|
||||
python3 - <<'PY'
|
||||
@@ -59,6 +88,7 @@ import sys
|
||||
|
||||
root = pathlib.Path('.')
|
||||
path = root / 'backlog' / 'features.json'
|
||||
level_choices = {'low', 'med', 'high'}
|
||||
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding='utf-8'))
|
||||
@@ -66,7 +96,9 @@ except Exception as e:
|
||||
print(f"[FAIL] backlog/features.json inválido: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
valid = set(data.get('rules', {}).get('valid_status', ["pending", "in_progress", "blocked", "done"]))
|
||||
rules = data.get('rules', {})
|
||||
valid_status = set(rules.get('valid_status', ["pending", "in_progress", "blocked", "done"]))
|
||||
valid_types = set(rules.get('valid_types', ["feature", "fix", "bug", "chore"]))
|
||||
features = data.get('features', [])
|
||||
if not isinstance(features, list):
|
||||
print('[FAIL] features debe ser una lista')
|
||||
@@ -85,25 +117,65 @@ if len(in_progress) > 1:
|
||||
for f in features:
|
||||
fid = str(f.get('id', '')).strip()
|
||||
status = f.get('status')
|
||||
if status not in valid:
|
||||
title = str(f.get('title', '')).strip()
|
||||
acceptance = f.get('acceptance')
|
||||
gates = f.get('gates', {})
|
||||
|
||||
if not fid:
|
||||
print('[FAIL] Hay una feature sin id')
|
||||
sys.exit(1)
|
||||
if not title:
|
||||
print(f"[FAIL] Feature {fid} sin title")
|
||||
sys.exit(1)
|
||||
if status not in valid_status:
|
||||
print(f"[FAIL] Estado inválido en feature {fid}: {status}")
|
||||
sys.exit(1)
|
||||
if not isinstance(acceptance, list) or not acceptance or any(not str(item).strip() for item in acceptance):
|
||||
print(f"[FAIL] Feature {fid} debe tener acceptance como lista no vacía")
|
||||
sys.exit(1)
|
||||
|
||||
ticket_type = f.get('type')
|
||||
if ticket_type is not None and ticket_type not in valid_types:
|
||||
print(f"[FAIL] Feature {fid} tiene type inválido: {ticket_type}")
|
||||
sys.exit(1)
|
||||
|
||||
for field in ('priority', 'risk'):
|
||||
value = f.get(field)
|
||||
if value is not None and value not in level_choices:
|
||||
print(f"[FAIL] Feature {fid} tiene {field} inválido: {value}")
|
||||
sys.exit(1)
|
||||
|
||||
for field in ('scope_in', 'scope_out'):
|
||||
value = f.get(field)
|
||||
if value is not None:
|
||||
if not isinstance(value, list) or any(not str(item).strip() for item in value):
|
||||
print(f"[FAIL] Feature {fid} tiene {field} inválido")
|
||||
sys.exit(1)
|
||||
|
||||
if gates:
|
||||
for gate_name in ('review', 'security', 'qa'):
|
||||
gate_value = gates.get(gate_name)
|
||||
if not isinstance(gate_value, bool):
|
||||
print(f"[FAIL] Feature {fid} tiene gates.{gate_name} inválido")
|
||||
sys.exit(1)
|
||||
|
||||
if status == 'done':
|
||||
d = root / 'work' / 'artifacts' / fid
|
||||
req = ['reviewer.json', 'security.json', 'qa.json', 'leader-close.json', 'documenter.md']
|
||||
req = ['reviewer.json', 'security.json', 'qa.json', 'leader-close.json', 'documenter.md', 'publish.json']
|
||||
missing = [name for name in req if not (d / name).is_file()]
|
||||
if missing:
|
||||
print(f"[FAIL] Feature {fid} done sin artefactos: {', '.join(missing)}")
|
||||
sys.exit(1)
|
||||
|
||||
expected = {
|
||||
'reviewer.json': 'reviewer',
|
||||
'security.json': 'security',
|
||||
'qa.json': 'qa',
|
||||
'leader-close.json': 'leader',
|
||||
'reviewer.json': ('reviewer', 'APPROVED'),
|
||||
'security.json': ('security', 'APPROVED'),
|
||||
'qa.json': ('qa', 'APPROVED'),
|
||||
'leader-close.json': ('leader', 'APPROVED'),
|
||||
'publish.json': ('leader', 'PUBLISHED'),
|
||||
}
|
||||
for filename, agent in expected.items():
|
||||
for filename, rule in expected.items():
|
||||
agent, verdict = rule
|
||||
try:
|
||||
obj = json.loads((d / filename).read_text(encoding='utf-8'))
|
||||
except Exception as e:
|
||||
@@ -113,8 +185,11 @@ for f in features:
|
||||
if obj.get('agent') != agent:
|
||||
print(f"[FAIL] {fid}/{filename} agent debe ser '{agent}'")
|
||||
sys.exit(1)
|
||||
if obj.get('verdict') != 'APPROVED':
|
||||
print(f"[FAIL] {fid}/{filename} no está APPROVED")
|
||||
if obj.get('verdict') != verdict:
|
||||
print(f"[FAIL] {fid}/{filename} no está {verdict}")
|
||||
sys.exit(1)
|
||||
if filename == 'publish.json' and obj.get('pushed') is not True:
|
||||
print(f"[FAIL] {fid}/{filename} debe tener pushed=true")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"[OK] backlog válido ({len(features)} features)")
|
||||
|
||||
Reference in New Issue
Block a user