From 70ff54cfa3e7ef66bc11f05a24095d13c7d96985 Mon Sep 17 00:00:00 2001 From: rikrdo Date: Mon, 17 Aug 2026 18:59:39 +0200 Subject: [PATCH] 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 --- AGENTS.md | 5 +- .../pi/extensions/orquestra-status/index.ts | 8 + scripts/install.sh | 29 +++- scripts/verify.sh | 58 +++++++ work/runtime-status.json | 141 +----------------- 5 files changed, 103 insertions(+), 138 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2a3deb8..b769fde 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,10 @@ - Si `verify.sh` falla, no se cierra la feature. - **Solo se puede escribir en carpetas permitidas**: `project/`, `tests/`, `work/`, `backlog/`, `spec/`, `harness/`, `scripts/`, `platforms/`, `docs/`. Cualquier otra escritura será bloqueada por la extensión `orquestra-status`. - **Al cerrar una feature, el leader ejecuta `scripts/close_feature.py `** que valida gates, actualiza el backlog y hace commit/push automáticamente. -- **NUNCA editar `backlog/features.json` directamente**. Usar siempre `scripts/close_feature.py` para cerrar features. +- **NUNCA editar `backlog/features.json` directamente**. Usar siempre `scripts/close_feature.py` para cerrar features. La extensión `orquestra-status` bloquea escrituras directas a este archivo. +- **Directorios prohibidos**: `specs/` (plural), `apps/`, `frontend/`, `src/`, `lib/`. El código del producto va en `project/`. +- **Nomenclatura de gates**: usar `reviewer`, no `review`. El `verify.sh` valida que no se usen nombres deprecated. +- **Schema de features**: todas las features deben tener al menos `id` y `status`. Las gates deben usar nombres válidos: `reviewer`, `security`, `qa`, `close`, `leader`. ## Reentrada - Releer `work/current.md`, `work/runtime-status.json` y artefactos de la feature activa. diff --git a/platforms/pi/extensions/orquestra-status/index.ts b/platforms/pi/extensions/orquestra-status/index.ts index 88111e3..5325b4a 100644 --- a/platforms/pi/extensions/orquestra-status/index.ts +++ b/platforms/pi/extensions/orquestra-status/index.ts @@ -215,6 +215,14 @@ function guardWriteOrEdit(event: ToolCallEvent, ctx: ExtensionContext): { block? }; } + // Bloquear escrituras directas a backlog/features.json (debe usar close_feature.py) + if (relPath === "backlog/features.json") { + return { + block: true, + reason: `Orquestra bloqueó '${relPath}': NUNCA edites backlog/features.json directamente. Usa scripts/close_feature.py para cerrar features. Este script valida gates y actualiza el backlog automáticamente.`, + }; + } + if (isRootProductFile(relPath)) { return { block: true, diff --git a/scripts/install.sh b/scripts/install.sh index 9fe725c..90a028c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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 } diff --git a/scripts/verify.sh b/scripts/verify.sh index b654a0c..8bea150 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -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 diff --git a/work/runtime-status.json b/work/runtime-status.json index a2695ee..9e3f58e 100644 --- a/work/runtime-status.json +++ b/work/runtime-status.json @@ -1,138 +1,11 @@ { - "feature_id": "F-001", - "stage": "intake", + "feature_id": null, + "stage": "idle", "agent": "leader", - "action": "Gate validation reforzado: agent_status.py bloquea close sin gates + close_feature.py valida antes de actualizar backlog", - "state": "done", + "action": "Sin ejecución activa", + "state": "waiting", "next_agent": "leader", - "waiting_for": "Probar en producción", - "updated_at": "2026-08-17T05:25:08Z", - "timeline": [ - { - "ts": "2026-08-15T07:05:52Z", - "agent": "leader", - "stage": "intake", - "state": "running", - "message": "status extension hangs" - }, - { - "ts": "2026-08-15T07:08:02Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "verify.sh OK" - }, - { - "ts": "2026-08-15T07:12:07Z", - "agent": "leader", - "stage": "intake", - "state": "running", - "message": "emoji overlap + context handoff" - }, - { - "ts": "2026-08-15T07:13:29Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "verify.sh OK" - }, - { - "ts": "2026-08-15T07:16:37Z", - "agent": "leader", - "stage": "intake", - "state": "running", - "message": "fresh Pi process per stage" - }, - { - "ts": "2026-08-15T07:18:21Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "py_compile + dry-run + verify.sh OK" - }, - { - "ts": "2026-08-15T13:20:09Z", - "agent": "leader", - "stage": "intake", - "state": "running", - "message": "context sharing doc" - }, - { - "ts": "2026-08-15T13:21:11Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "verify.sh + run_stage dry-run OK" - }, - { - "ts": "2026-08-15T15:56:29Z", - "agent": "leader", - "stage": "intake", - "state": "running", - "message": "parent session launcher" - }, - { - "ts": "2026-08-15T15:58:00Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "verify.sh OK; dry-run confirms fresh Pi flags" - }, - { - "ts": "2026-08-15T16:08:27Z", - "agent": "leader", - "stage": "intake", - "state": "running", - "message": "selective Engram save + install target" - }, - { - "ts": "2026-08-15T16:09:07Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "source verify OK; target verify OK; target dry-run OK" - }, - { - "ts": "2026-08-16T22:06:15Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "ALLOWED_WRITE_DIRS + AGENTS.md + docs actualizados" - }, - { - "ts": "2026-08-17T05:19:30Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "scripts/commit_feature.sh + workflow post_actions + docs actualizados" - }, - { - "ts": "2026-08-17T05:24:32Z", - "agent": "leader", - "stage": "close", - "state": "running", - "message": "Starting close" - }, - { - "ts": "2026-08-17T05:24:32Z", - "agent": "leader", - "stage": "close", - "state": "done", - "message": "Test close with all gates" - }, - { - "ts": "2026-08-17T05:24:51Z", - "agent": "leader", - "stage": "close", - "state": "running", - "message": "Starting close" - }, - { - "ts": "2026-08-17T05:25:08Z", - "agent": "leader", - "stage": "intake", - "state": "done", - "message": "Scripts: close_feature.py, agent_status.py actualizado, run_stage.py con instrucción de no editar backlog" - } - ] + "waiting_for": "Seleccionar una feature pending y actualizar este estado", + "updated_at": "2026-08-17T16:26:21Z", + "timeline": [] }