refactor: reinforce harness to prevent contract violations
- verify.sh: validate prohibited dirs, gate nomenclature, feature schema, runtime consistency - orquestra-status: block direct writes to backlog/features.json - install.sh: add build artifacts to .gitignore template - AGENTS.md: document prohibited dirs, gate nomenclature, feature schema - Reset runtime-status to idle state
This commit is contained in:
@@ -79,16 +79,39 @@ append_gitignore_block() {
|
||||
local gitignore=$1
|
||||
touch "$gitignore"
|
||||
if ! grep -q '^# BEGIN ORQUESTRA$' "$gitignore"; then
|
||||
cat >>"$gitignore" <<'EOF'
|
||||
|
||||
cat >>"$gitignore" <<'INNEREOF'
|
||||
# BEGIN ORQUESTRA
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
.codegraph/
|
||||
.atl/
|
||||
|
||||
# Build artifacts
|
||||
.next/
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
# END ORQUESTRA
|
||||
EOF
|
||||
INNEREOF
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,16 @@ for d in "${required_dirs[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for prohibited directories
|
||||
prohibited_dirs=("specs" "apps" "frontend" "src" "lib")
|
||||
for d in "${prohibited_dirs[@]}"; do
|
||||
if [ -d "$d" ]; then
|
||||
fail "Directorio prohibido encontrado: $d/ (usar project/ para código)"
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
done
|
||||
ok "Sin directorios prohibidos (specs/, apps/, frontend/, src/, lib/)"
|
||||
|
||||
root_product_files=$(find . -mindepth 1 -maxdepth 1 -type f \( \
|
||||
-name '*.py' -o -name '*.js' -o -name '*.ts' -o -name '*.go' -o -name '*.rs' -o \
|
||||
-name '*.java' -o -name '*.php' -o -name '*.rb' \
|
||||
@@ -140,14 +150,39 @@ if len(in_progress) > 1:
|
||||
print(f"[FAIL] Hay {len(in_progress)} features in_progress (máximo 1)")
|
||||
sys.exit(1)
|
||||
|
||||
# Required fields for feature schema
|
||||
required_fields = ['id', 'status']
|
||||
# Valid gate names
|
||||
valid_gate_names = {'reviewer', 'security', 'qa', 'close', 'leader'}
|
||||
|
||||
for f in features:
|
||||
fid = str(f.get('id', '')).strip()
|
||||
status = f.get('status')
|
||||
|
||||
# Check required fields
|
||||
for field in required_fields:
|
||||
if field not in f:
|
||||
print(f"[FAIL] Feature {fid} missing required field: {field}")
|
||||
sys.exit(1)
|
||||
|
||||
if status not in valid:
|
||||
print(f"[FAIL] Estado inválido en feature {fid}: {status}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate gate nomenclature
|
||||
gates = f.get('gates', {})
|
||||
if 'review' in gates:
|
||||
print(f"[FAIL] Feature {fid} uses deprecated gate name 'review' (should be 'reviewer')")
|
||||
sys.exit(1)
|
||||
|
||||
# Check for invalid gate names
|
||||
for gate_name in gates.keys():
|
||||
if gate_name not in valid_gate_names:
|
||||
print(f"[FAIL] Feature {fid} has invalid gate name: {gate_name}")
|
||||
sys.exit(1)
|
||||
|
||||
if status == 'done':
|
||||
# Check artifacts exist
|
||||
d = root / 'work' / 'artifacts' / fid
|
||||
req = ['reviewer.json', 'security.json', 'qa.json', 'leader-close.json']
|
||||
missing = [name for name in req if not (d / name).is_file()]
|
||||
@@ -155,6 +190,11 @@ for f in features:
|
||||
print(f"[FAIL] Feature {fid} done sin artefactos: {', '.join(missing)}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check gates in backlog match artifacts
|
||||
if not gates.get('reviewer') or not gates.get('security') or not gates.get('qa'):
|
||||
print(f"[FAIL] Feature {fid} done sin gates aprobados en backlog")
|
||||
sys.exit(1)
|
||||
|
||||
expected = {
|
||||
'reviewer.json': 'reviewer',
|
||||
'security.json': 'security',
|
||||
@@ -191,6 +231,24 @@ if not isinstance(runtime.get('timeline'), list):
|
||||
print('[FAIL] work/runtime-status.json timeline debe ser una lista')
|
||||
sys.exit(1)
|
||||
|
||||
# Check runtime consistency with backlog
|
||||
runtime_feature_id = runtime.get('feature_id')
|
||||
if runtime_feature_id:
|
||||
feature_in_backlog = None
|
||||
for f in features:
|
||||
if f.get('id') == runtime_feature_id:
|
||||
feature_in_backlog = f
|
||||
break
|
||||
|
||||
if feature_in_backlog:
|
||||
runtime_state = runtime.get('state')
|
||||
backlog_status = feature_in_backlog.get('status')
|
||||
|
||||
# If feature is pending in backlog but running/done in runtime, that's inconsistent
|
||||
if backlog_status == 'pending' and runtime_state in ['running', 'done']:
|
||||
print(f"[FAIL] Inconsistencia: {runtime_feature_id} está pending en backlog pero {runtime_state} en runtime-status")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"[OK] backlog válido ({len(features)} features)")
|
||||
print('[OK] runtime-status válido')
|
||||
PY
|
||||
|
||||
Reference in New Issue
Block a user