fix(ops): format list fields as JSON arrays, filter out placeholder strings, and update prompt template rules

This commit is contained in:
michele 2026-08-10 01:28:15 +02:00
parent 1fbe2a746d
commit 0ac85f8b92
3 changed files with 187 additions and 69 deletions

View File

@ -23,3 +23,8 @@ TEST_ESEGUITI:
BLOCCO_DATI: si | no BLOCCO_DATI: si | no
NOTE: NOTE:
- <nota> - <nota>
REGOLE RIGIDE PER LE LISTE (ABSORBED_LEGACY_FRAGMENTS, OPEN_LEGACY_FRAGMENTS, DATES_USED, TEST_ESEGUITI, NOTE):
- Ogni elemento della lista deve iniziare con '- '.
- Quando una lista non ha elementi: lasciare soltanto l'intestazione (es. OPEN_LEGACY_FRAGMENTS:) e non inserire alcuna riga con trattino.
- E VIETATO tassativamente scrivere 'none', 'nessuno', 'nessuna', 'n/a' o 'non applicabile'.

View File

@ -4,6 +4,39 @@ import json
import re import re
import argparse import argparse
PLACEHOLDERS = {"none", "nessuno", "nessuna", "n/a", "non applicabile", "null"}
def clean_list_item(item: str) -> str:
item = item.strip()
if item.startswith("- "):
item = item[2:].strip()
elif item.startswith("-"):
item = item[1:].strip()
return item
def is_placeholder(item: str) -> bool:
clean = clean_list_item(item).lower()
return clean in PLACEHOLDERS or not clean
def parse_list_field(val) -> list:
if val is None:
return []
items = []
if isinstance(val, list):
for el in val:
s = str(el).strip()
if not is_placeholder(s):
items.append(clean_list_item(s))
elif isinstance(val, str):
lines = val.strip().splitlines()
for line in lines:
s = line.strip()
if not s:
continue
if not is_placeholder(s):
items.append(clean_list_item(s))
return items
def parse_output(text: str) -> dict: def parse_output(text: str) -> dict:
fields = { fields = {
"task_id": None, "task_id": None,
@ -13,11 +46,11 @@ def parse_output(text: str) -> dict:
"commit": None, "commit": None,
"consolidated_unit_id": None, "consolidated_unit_id": None,
"blocco_dati": None, "blocco_dati": None,
"dates_used": None, "dates_used": [],
"test_eseguiti": None, "test_eseguiti": [],
"note": None, "note": [],
"absorbed_legacy_fragments": None, "absorbed_legacy_fragments": [],
"open_legacy_fragments": None, "open_legacy_fragments": [],
"raw_text": text "raw_text": text
} }
@ -27,33 +60,71 @@ def parse_output(text: str) -> dict:
data = json.loads(json_match.group(0)) data = json.loads(json_match.group(0))
if isinstance(data, dict): if isinstance(data, dict):
source_dict = data.get("parsed", data) source_dict = data.get("parsed", data)
for k in fields: if isinstance(source_dict, dict):
if k in source_dict and source_dict[k] is not None: for k in fields:
fields[k] = source_dict[k] if k in ["raw_text"]:
continue
if k in ["dates_used", "test_eseguiti", "note", "absorbed_legacy_fragments", "open_legacy_fragments"]:
if k in source_dict and source_dict[k] is not None:
fields[k] = parse_list_field(source_dict[k])
else:
if k in source_dict and source_dict[k] is not None:
fields[k] = str(source_dict[k]).strip()
except Exception: except Exception:
pass pass
patterns = { HEADER_MAP = {
"task_id": r"(?:TASK_ID|task_id):\s*(.+)", "TASK_ID": "task_id",
"esito_205": r"(?:ESITO_205|esito_205):\s*(.+)", "ESITO_205": "esito_205",
"repository": r"(?:REPOSITORY|repository):\s*(.+)", "REPOSITORY": "repository",
"branch": r"(?:BRANCH|branch):\s*(.+)", "BRANCH": "branch",
"commit": r"(?:COMMIT|commit):\s*(.+)", "COMMIT": "commit",
"consolidated_unit_id": r"(?:CONSOLIDATED_UNIT_ID|UNITA_ID|consolidated_unit_id):\s*(.+)", "CONSOLIDATED_UNIT_ID": "consolidated_unit_id",
"blocco_dati": r"(?:BLOCCO_DATI|blocco_dati):\s*(.+)", "UNITA_ID": "consolidated_unit_id",
"dates_used": r"(?:DATES_USED|dates_used):\s*(.+)", "BLOCCO_DATI": "blocco_dati",
"test_eseguiti": r"(?:TEST_ESEGUITI|test_eseguiti):\s*(.+)", "ABSORBED_LEGACY_FRAGMENTS": "absorbed_legacy_fragments",
"note": r"(?:NOTE|note):\s*(.+)", "OPEN_LEGACY_FRAGMENTS": "open_legacy_fragments",
"absorbed_legacy_fragments": r"(?:ABSORBED_LEGACY_FRAGMENTS|absorbed_legacy_fragments):\s*(.+)", "DATES_USED": "dates_used",
"open_legacy_fragments": r"(?:OPEN_LEGACY_FRAGMENTS|open_legacy_fragments):\s*(.+)" "TEST_ESEGUITI": "test_eseguiti",
"NOTE": "note",
} }
for key, pat in patterns.items(): lines = text.splitlines()
if fields[key] is None: current_key = None
m = re.search(pat, text, re.IGNORECASE)
for line in lines:
stripped = line.strip()
if not stripped:
continue
header_match = None
for h_name, field_key in HEADER_MAP.items():
pattern = rf"^(?:{h_name}|{field_key}):\s*(.*)$"
m = re.match(pattern, stripped, re.IGNORECASE)
if m: if m:
val = m.group(1).strip() header_match = (field_key, m.group(1).strip())
fields[key] = val break
if header_match:
field_key, inline_val = header_match
current_key = field_key
if field_key in ["dates_used", "test_eseguiti", "note", "absorbed_legacy_fragments", "open_legacy_fragments"]:
if inline_val and not is_placeholder(inline_val):
fields[field_key].append(clean_list_item(inline_val))
else:
if not fields[field_key] and inline_val:
fields[field_key] = inline_val
elif current_key and current_key in ["dates_used", "test_eseguiti", "note", "absorbed_legacy_fragments", "open_legacy_fragments"]:
if not is_placeholder(stripped):
fields[current_key].append(clean_list_item(stripped))
for k in ["dates_used", "test_eseguiti", "note", "absorbed_legacy_fragments", "open_legacy_fragments"]:
cleaned = []
for el in fields[k]:
c = clean_list_item(str(el))
if c and not is_placeholder(c) and c not in cleaned:
cleaned.append(c)
fields[k] = cleaned
return fields return fields
@ -81,9 +152,6 @@ def validate_fields(fields: dict, expected_repo: str = None, expected_branch: st
"esito_205", "esito_205",
"consolidated_unit_id", "consolidated_unit_id",
"blocco_dati", "blocco_dati",
"dates_used",
"test_eseguiti",
"note",
] ]
for key in mandatory_single_fields: for key in mandatory_single_fields:
@ -91,12 +159,21 @@ def validate_fields(fields: dict, expected_repo: str = None, expected_branch: st
if val is None or (isinstance(val, str) and not val.strip()): if val is None or (isinstance(val, str) and not val.strip()):
missing.append(key) missing.append(key)
abs_frag = fields.get("absorbed_legacy_fragments") mandatory_list_fields = [
open_frag = fields.get("open_legacy_fragments") "dates_used",
has_abs = abs_frag is not None and (not isinstance(abs_frag, str) or bool(str(abs_frag).strip())) "test_eseguiti",
has_open = open_frag is not None and (not isinstance(open_frag, str) or bool(str(open_frag).strip())) "note",
]
if not (has_abs or has_open): for key in mandatory_list_fields:
val = fields.get(key)
if not isinstance(val, list) or len(val) == 0:
missing.append(key)
abs_frag = fields.get("absorbed_legacy_fragments") or []
open_frag = fields.get("open_legacy_fragments") or []
if len(abs_frag) == 0 and len(open_frag) == 0:
missing.append("absorbed_legacy_fragments|open_legacy_fragments") missing.append("absorbed_legacy_fragments|open_legacy_fragments")
dedup_missing = [] dedup_missing = []

View File

@ -12,9 +12,7 @@ REAL_COMMIT="$(git -C "$REPO_DIR" rev-parse HEAD)"
echo "=== ESECUZIONE TEST AUTOMATICI WRAPPER E PARSER AGY 1.1.11 ===" echo "=== ESECUZIONE TEST AUTOMATICI WRAPPER E PARSER AGY 1.1.11 ==="
# -------------------------------------------------------------
# Test 1: Output Jetski negato -> wrapper fallisce # Test 1: Output Jetski negato -> wrapper fallisce
# -------------------------------------------------------------
echo -n "Test 1: Output Jetski negato -> " echo -n "Test 1: Output Jetski negato -> "
TEST_BIN_DIR="$(mktemp -d)" TEST_BIN_DIR="$(mktemp -d)"
cat << 'EOF' > "$TEST_BIN_DIR/agy-ultra" cat << 'EOF' > "$TEST_BIN_DIR/agy-ultra"
@ -37,9 +35,7 @@ else
exit 1 exit 1
fi fi
# -------------------------------------------------------------
# Test 2: Output vuoto -> wrapper fallisce # Test 2: Output vuoto -> wrapper fallisce
# -------------------------------------------------------------
echo -n "Test 2: Output vuoto -> " echo -n "Test 2: Output vuoto -> "
TEST_BIN_DIR="$(mktemp -d)" TEST_BIN_DIR="$(mktemp -d)"
cat << 'EOF' > "$TEST_BIN_DIR/agy-ultra" cat << 'EOF' > "$TEST_BIN_DIR/agy-ultra"
@ -62,20 +58,22 @@ else
exit 1 exit 1
fi fi
# -------------------------------------------------------------
# Test 3: task_id null -> parser fallisce # Test 3: task_id null -> parser fallisce
# -------------------------------------------------------------
echo -n "Test 3: task_id null -> " echo -n "Test 3: task_id null -> "
INCOMPLETE_REPORT="ESITO_205: riuscito INCOMPLETE_REPORT="ESITO_205: riuscito
REPOSITORY: ${REAL_REPO} REPOSITORY: ${REAL_REPO}
BRANCH: ${REAL_BRANCH} BRANCH: ${REAL_BRANCH}
COMMIT: ${REAL_COMMIT} COMMIT: ${REAL_COMMIT}
CONSOLIDATED_UNIT_ID: 1545 CONSOLIDATED_UNIT_ID: 1545
DATES_USED: 2026-08-10 DATES_USED:
TEST_ESEGUITI: ./vendor/bin/pest - 2026-08-10
TEST_ESEGUITI:
- pest
BLOCCO_DATI: no BLOCCO_DATI: no
NOTE: Incompleto NOTE:
ABSORBED_LEGACY_FRAGMENTS: id_cond=12" - Incompleto
ABSORBED_LEGACY_FRAGMENTS:
- id_cond=12"
set +e set +e
TEST3_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$INCOMPLETE_REPORT" 2>&1)" TEST3_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$INCOMPLETE_REPORT" 2>&1)"
@ -90,61 +88,99 @@ else
exit 1 exit 1
fi fi
# ------------------------------------------------------------- # Test 4: '- nessuno' viene scartato e restituisce lista vuota []
# Test 4: Report completo -> passa echo -n "Test 4: '- nessuno' viene scartato -> "
# ------------------------------------------------------------- NONE_REPORT="TASK_ID: CT-2026-08-10-TEST-NONE
echo -n "Test 4: Report completo -> "
COMPLETE_REPORT="TASK_ID: CT-2026-08-10-TEST-001
ESITO_205: riuscito ESITO_205: riuscito
REPOSITORY: ${REAL_REPO} REPOSITORY: ${REAL_REPO}
BRANCH: ${REAL_BRANCH} BRANCH: ${REAL_BRANCH}
COMMIT: ${REAL_COMMIT} COMMIT: ${REAL_COMMIT}
CONSOLIDATED_UNIT_ID: 1545 CONSOLIDATED_UNIT_ID: 1545
DATES_USED: 2026-08-10 ABSORBED_LEGACY_FRAGMENTS:
TEST_ESEGUITI: ./vendor/bin/pest - artisan-ultra-real-24825cbb
OPEN_LEGACY_FRAGMENTS:
- nessuno
DATES_USED:
- 2026-08-09
TEST_ESEGUITI:
- test
BLOCCO_DATI: no BLOCCO_DATI: no
NOTE: Task completato correttamente NOTE:
ABSORBED_LEGACY_FRAGMENTS: id_cond=12" - ARTISAN_ULTRA_OK"
set +e set +e
TEST4_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$COMPLETE_REPORT" 2>&1)" TEST4_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$NONE_REPORT" 2>&1)"
TEST4_EXIT=$? TEST4_EXIT=$?
set -e set -e
if [ $TEST4_EXIT -eq 0 ] && [[ "$TEST4_OUT" == *'"ok": true'* ]]; then if [ $TEST4_EXIT -eq 0 ] && [[ "$TEST4_OUT" == *'"open_legacy_fragments": []'* ]]; then
echo "PASS (Exit Code: $TEST4_EXIT)" echo "PASS (Exit Code: $TEST4_EXIT)"
else else
echo "FAIL (Expected exit code 0 with ok: true)" echo "FAIL (Expected open_legacy_fragments to be empty array [])"
echo "$TEST4_OUT" echo "$TEST4_OUT"
exit 1 exit 1
fi fi
# ------------------------------------------------------------- # Test 5: Liste restituite come array e smoke completo produce ok=true
# Test 5: Git diverso dal reale -> fallisce echo -n "Test 5: Smoke completo con liste array -> "
# ------------------------------------------------------------- COMPLETE_REPORT="TASK_ID: task-348c024293
echo -n "Test 5: Git diverso dal reale -> " ESITO_205: riuscito
REPOSITORY: ${REAL_REPO}
BRANCH: ${REAL_BRANCH}
COMMIT: ${REAL_COMMIT}
CONSOLIDATED_UNIT_ID: SMOKE-24825CBB
ABSORBED_LEGACY_FRAGMENTS:
- artisan-ultra-real-24825cbb
OPEN_LEGACY_FRAGMENTS:
DATES_USED:
- 2026-08-09
TEST_ESEGUITI:
- lettura AGENTS.md e parser rigoroso
BLOCCO_DATI: no
NOTE:
- ARTISAN_ULTRA_24825CBB_REAL_OK"
set +e
TEST5_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$COMPLETE_REPORT" 2>&1)"
TEST5_EXIT=$?
set -e
if [ $TEST5_EXIT -eq 0 ] && [[ "$TEST5_OUT" == *'"ok": true'* ]] && [[ "$TEST5_OUT" == *'"absorbed_legacy_fragments": ['* ]]; then
echo "PASS (Exit Code: $TEST5_EXIT)"
else
echo "FAIL (Expected ok: true with array fields)"
echo "$TEST5_OUT"
exit 1
fi
# Test 6: Git diverso dal reale -> fallisce
echo -n "Test 6: Git diverso dal reale -> "
MISMATCH_REPORT="TASK_ID: CT-2026-08-10-TEST-001 MISMATCH_REPORT="TASK_ID: CT-2026-08-10-TEST-001
ESITO_205: riuscito ESITO_205: riuscito
REPOSITORY: ssh://git@wrong-repo.git REPOSITORY: ssh://git@wrong-repo.git
BRANCH: wrong-branch BRANCH: wrong-branch
COMMIT: deadbeef1234 COMMIT: deadbeef1234
CONSOLIDATED_UNIT_ID: 1545 CONSOLIDATED_UNIT_ID: 1545
DATES_USED: 2026-08-10 DATES_USED:
TEST_ESEGUITI: ./vendor/bin/pest - 2026-08-10
TEST_ESEGUITI:
- pest
BLOCCO_DATI: no BLOCCO_DATI: no
NOTE: Mismatch test NOTE:
ABSORBED_LEGACY_FRAGMENTS: id_cond=12" - Mismatch test
ABSORBED_LEGACY_FRAGMENTS:
- id_cond=12"
set +e set +e
TEST5_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$MISMATCH_REPORT" 2>&1)" TEST6_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$MISMATCH_REPORT" 2>&1)"
TEST5_EXIT=$? TEST6_EXIT=$?
set -e set -e
if [ $TEST5_EXIT -eq 1 ] && [[ "$TEST5_OUT" == *'"ok": false'* ]] && [[ "$TEST5_OUT" == *'"repository"'* ]]; then if [ $TEST6_EXIT -eq 1 ] && [[ "$TEST6_OUT" == *'"ok": false'* ]] && [[ "$TEST6_OUT" == *'"repository"'* ]]; then
echo "PASS (Exit Code: $TEST5_EXIT)" echo "PASS (Exit Code: $TEST6_EXIT)"
else else
echo "FAIL (Expected exit code 1 with git mismatch)" echo "FAIL (Expected exit code 1 with git mismatch)"
echo "$TEST5_OUT" echo "$TEST6_OUT"
exit 1 exit 1
fi fi