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
NOTE:
- <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 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:
fields = {
"task_id": None,
@ -13,11 +46,11 @@ def parse_output(text: str) -> dict:
"commit": None,
"consolidated_unit_id": None,
"blocco_dati": None,
"dates_used": None,
"test_eseguiti": None,
"note": None,
"absorbed_legacy_fragments": None,
"open_legacy_fragments": None,
"dates_used": [],
"test_eseguiti": [],
"note": [],
"absorbed_legacy_fragments": [],
"open_legacy_fragments": [],
"raw_text": text
}
@ -27,33 +60,71 @@ def parse_output(text: str) -> dict:
data = json.loads(json_match.group(0))
if isinstance(data, dict):
source_dict = data.get("parsed", data)
for k in fields:
if k in source_dict and source_dict[k] is not None:
fields[k] = source_dict[k]
if isinstance(source_dict, dict):
for k in fields:
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:
pass
patterns = {
"task_id": r"(?:TASK_ID|task_id):\s*(.+)",
"esito_205": r"(?:ESITO_205|esito_205):\s*(.+)",
"repository": r"(?:REPOSITORY|repository):\s*(.+)",
"branch": r"(?:BRANCH|branch):\s*(.+)",
"commit": r"(?:COMMIT|commit):\s*(.+)",
"consolidated_unit_id": r"(?:CONSOLIDATED_UNIT_ID|UNITA_ID|consolidated_unit_id):\s*(.+)",
"blocco_dati": r"(?:BLOCCO_DATI|blocco_dati):\s*(.+)",
"dates_used": r"(?:DATES_USED|dates_used):\s*(.+)",
"test_eseguiti": r"(?:TEST_ESEGUITI|test_eseguiti):\s*(.+)",
"note": r"(?:NOTE|note):\s*(.+)",
"absorbed_legacy_fragments": r"(?:ABSORBED_LEGACY_FRAGMENTS|absorbed_legacy_fragments):\s*(.+)",
"open_legacy_fragments": r"(?:OPEN_LEGACY_FRAGMENTS|open_legacy_fragments):\s*(.+)"
HEADER_MAP = {
"TASK_ID": "task_id",
"ESITO_205": "esito_205",
"REPOSITORY": "repository",
"BRANCH": "branch",
"COMMIT": "commit",
"CONSOLIDATED_UNIT_ID": "consolidated_unit_id",
"UNITA_ID": "consolidated_unit_id",
"BLOCCO_DATI": "blocco_dati",
"ABSORBED_LEGACY_FRAGMENTS": "absorbed_legacy_fragments",
"OPEN_LEGACY_FRAGMENTS": "open_legacy_fragments",
"DATES_USED": "dates_used",
"TEST_ESEGUITI": "test_eseguiti",
"NOTE": "note",
}
for key, pat in patterns.items():
if fields[key] is None:
m = re.search(pat, text, re.IGNORECASE)
lines = text.splitlines()
current_key = None
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:
val = m.group(1).strip()
fields[key] = val
header_match = (field_key, m.group(1).strip())
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
@ -81,9 +152,6 @@ def validate_fields(fields: dict, expected_repo: str = None, expected_branch: st
"esito_205",
"consolidated_unit_id",
"blocco_dati",
"dates_used",
"test_eseguiti",
"note",
]
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()):
missing.append(key)
abs_frag = fields.get("absorbed_legacy_fragments")
open_frag = fields.get("open_legacy_fragments")
has_abs = abs_frag is not None and (not isinstance(abs_frag, str) or bool(str(abs_frag).strip()))
has_open = open_frag is not None and (not isinstance(open_frag, str) or bool(str(open_frag).strip()))
mandatory_list_fields = [
"dates_used",
"test_eseguiti",
"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")
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 ==="
# -------------------------------------------------------------
# Test 1: Output Jetski negato -> wrapper fallisce
# -------------------------------------------------------------
echo -n "Test 1: Output Jetski negato -> "
TEST_BIN_DIR="$(mktemp -d)"
cat << 'EOF' > "$TEST_BIN_DIR/agy-ultra"
@ -37,9 +35,7 @@ else
exit 1
fi
# -------------------------------------------------------------
# Test 2: Output vuoto -> wrapper fallisce
# -------------------------------------------------------------
echo -n "Test 2: Output vuoto -> "
TEST_BIN_DIR="$(mktemp -d)"
cat << 'EOF' > "$TEST_BIN_DIR/agy-ultra"
@ -62,20 +58,22 @@ else
exit 1
fi
# -------------------------------------------------------------
# Test 3: task_id null -> parser fallisce
# -------------------------------------------------------------
echo -n "Test 3: task_id null -> "
INCOMPLETE_REPORT="ESITO_205: riuscito
REPOSITORY: ${REAL_REPO}
BRANCH: ${REAL_BRANCH}
COMMIT: ${REAL_COMMIT}
CONSOLIDATED_UNIT_ID: 1545
DATES_USED: 2026-08-10
TEST_ESEGUITI: ./vendor/bin/pest
DATES_USED:
- 2026-08-10
TEST_ESEGUITI:
- pest
BLOCCO_DATI: no
NOTE: Incompleto
ABSORBED_LEGACY_FRAGMENTS: id_cond=12"
NOTE:
- Incompleto
ABSORBED_LEGACY_FRAGMENTS:
- id_cond=12"
set +e
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
fi
# -------------------------------------------------------------
# Test 4: Report completo -> passa
# -------------------------------------------------------------
echo -n "Test 4: Report completo -> "
COMPLETE_REPORT="TASK_ID: CT-2026-08-10-TEST-001
# Test 4: '- nessuno' viene scartato e restituisce lista vuota []
echo -n "Test 4: '- nessuno' viene scartato -> "
NONE_REPORT="TASK_ID: CT-2026-08-10-TEST-NONE
ESITO_205: riuscito
REPOSITORY: ${REAL_REPO}
BRANCH: ${REAL_BRANCH}
COMMIT: ${REAL_COMMIT}
CONSOLIDATED_UNIT_ID: 1545
DATES_USED: 2026-08-10
TEST_ESEGUITI: ./vendor/bin/pest
ABSORBED_LEGACY_FRAGMENTS:
- artisan-ultra-real-24825cbb
OPEN_LEGACY_FRAGMENTS:
- nessuno
DATES_USED:
- 2026-08-09
TEST_ESEGUITI:
- test
BLOCCO_DATI: no
NOTE: Task completato correttamente
ABSORBED_LEGACY_FRAGMENTS: id_cond=12"
NOTE:
- ARTISAN_ULTRA_OK"
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=$?
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)"
else
echo "FAIL (Expected exit code 0 with ok: true)"
echo "FAIL (Expected open_legacy_fragments to be empty array [])"
echo "$TEST4_OUT"
exit 1
fi
# -------------------------------------------------------------
# Test 5: Git diverso dal reale -> fallisce
# -------------------------------------------------------------
echo -n "Test 5: Git diverso dal reale -> "
# Test 5: Liste restituite come array e smoke completo produce ok=true
echo -n "Test 5: Smoke completo con liste array -> "
COMPLETE_REPORT="TASK_ID: task-348c024293
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
ESITO_205: riuscito
REPOSITORY: ssh://git@wrong-repo.git
BRANCH: wrong-branch
COMMIT: deadbeef1234
CONSOLIDATED_UNIT_ID: 1545
DATES_USED: 2026-08-10
TEST_ESEGUITI: ./vendor/bin/pest
DATES_USED:
- 2026-08-10
TEST_ESEGUITI:
- pest
BLOCCO_DATI: no
NOTE: Mismatch test
ABSORBED_LEGACY_FRAGMENTS: id_cond=12"
NOTE:
- Mismatch test
ABSORBED_LEGACY_FRAGMENTS:
- id_cond=12"
set +e
TEST5_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$MISMATCH_REPORT" 2>&1)"
TEST5_EXIT=$?
TEST6_OUT="$(python3 "$PARSER" --expected-repo "$REAL_REPO" --expected-branch "$REAL_BRANCH" --expected-commit "$REAL_COMMIT" <<< "$MISMATCH_REPORT" 2>&1)"
TEST6_EXIT=$?
set -e
if [ $TEST5_EXIT -eq 1 ] && [[ "$TEST5_OUT" == *'"ok": false'* ]] && [[ "$TEST5_OUT" == *'"repository"'* ]]; then
echo "PASS (Exit Code: $TEST5_EXIT)"
if [ $TEST6_EXIT -eq 1 ] && [[ "$TEST6_OUT" == *'"ok": false'* ]] && [[ "$TEST6_OUT" == *'"repository"'* ]]; then
echo "PASS (Exit Code: $TEST6_EXIT)"
else
echo "FAIL (Expected exit code 1 with git mismatch)"
echo "$TEST5_OUT"
echo "$TEST6_OUT"
exit 1
fi