@andresmassello/uscha 1.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/bin/README.md +6 -0
- package/bin/uscha.js +28 -0
- package/package.json +38 -0
- package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +161 -0
- package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +124 -0
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +490 -0
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +5602 -0
- package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +161 -0
- package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +145 -0
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +112 -0
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.ps1 +22 -0
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.sh +26 -0
- package/uscha-kit/.claude/skills/uscha-mirador/mirador.template.html +586 -0
- package/uscha-kit/.claude/skills/uscha-mirador/telemetry-extract.py +130 -0
- package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +116 -0
- package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +79 -0
- package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +88 -0
- package/uscha-kit/.claude-plugin/plugin.json +24 -0
- package/uscha-kit/.codex-plugin/plugin.json +37 -0
- package/uscha-kit/CHANGELOG-1.10.0.md +84 -0
- package/uscha-kit/CHANGELOG-1.11.0.md +67 -0
- package/uscha-kit/CHANGELOG-1.12.0.md +46 -0
- package/uscha-kit/CHANGELOG-1.13.0.md +33 -0
- package/uscha-kit/CHANGELOG-1.14.0.md +42 -0
- package/uscha-kit/CHANGELOG-1.15.0.md +58 -0
- package/uscha-kit/CHANGELOG-1.16.0.md +55 -0
- package/uscha-kit/CHANGELOG-1.17.0.md +44 -0
- package/uscha-kit/CHANGELOG-1.18.0.md +42 -0
- package/uscha-kit/CHANGELOG-1.19.0.md +41 -0
- package/uscha-kit/CHANGELOG-1.2.2.md +16 -0
- package/uscha-kit/CHANGELOG-1.2.3.md +20 -0
- package/uscha-kit/CHANGELOG-1.2.4.md +10 -0
- package/uscha-kit/CHANGELOG-1.2.5.md +23 -0
- package/uscha-kit/CHANGELOG-1.2.6.md +11 -0
- package/uscha-kit/CHANGELOG-1.2.7.md +15 -0
- package/uscha-kit/CHANGELOG-1.2.8.md +24 -0
- package/uscha-kit/CHANGELOG-1.2.9.md +4 -0
- package/uscha-kit/CHANGELOG-1.20.0.md +29 -0
- package/uscha-kit/CHANGELOG-1.21.0.md +33 -0
- package/uscha-kit/CHANGELOG-1.22.0.md +60 -0
- package/uscha-kit/CHANGELOG-1.23.0.md +75 -0
- package/uscha-kit/CHANGELOG-1.24.0.md +50 -0
- package/uscha-kit/CHANGELOG-1.25.0.md +55 -0
- package/uscha-kit/CHANGELOG-1.26.0.md +70 -0
- package/uscha-kit/CHANGELOG-1.27.0.md +45 -0
- package/uscha-kit/CHANGELOG-1.28.0.md +35 -0
- package/uscha-kit/CHANGELOG-1.29.0.md +20 -0
- package/uscha-kit/CHANGELOG-1.3.0.md +74 -0
- package/uscha-kit/CHANGELOG-1.30.0.md +46 -0
- package/uscha-kit/CHANGELOG-1.31.0.md +59 -0
- package/uscha-kit/CHANGELOG-1.32.0.md +50 -0
- package/uscha-kit/CHANGELOG-1.33.0.md +46 -0
- package/uscha-kit/CHANGELOG-1.34.0.md +55 -0
- package/uscha-kit/CHANGELOG-1.35.0.md +30 -0
- package/uscha-kit/CHANGELOG-1.36.0.md +33 -0
- package/uscha-kit/CHANGELOG-1.37.0.md +41 -0
- package/uscha-kit/CHANGELOG-1.38.0.md +11 -0
- package/uscha-kit/CHANGELOG-1.39.0.md +14 -0
- package/uscha-kit/CHANGELOG-1.4.0.md +68 -0
- package/uscha-kit/CHANGELOG-1.40.0.md +16 -0
- package/uscha-kit/CHANGELOG-1.40.1.md +11 -0
- package/uscha-kit/CHANGELOG-1.5.0.md +64 -0
- package/uscha-kit/CHANGELOG-1.6.0.md +57 -0
- package/uscha-kit/CHANGELOG-1.7.0.md +74 -0
- package/uscha-kit/CHANGELOG-1.8.0.md +46 -0
- package/uscha-kit/CHANGELOG-1.9.0.md +112 -0
- package/uscha-kit/LICENSE +21 -0
- package/uscha-kit/README.md +497 -0
- package/uscha-kit/VERSION +1 -0
- package/uscha-kit/WORKBENCH.md +178 -0
- package/uscha-kit/hooks/block-approved-writes.ps1 +46 -0
- package/uscha-kit/hooks/hooks.json +15 -0
- package/uscha-kit/install-uscha.py +344 -0
- package/uscha-kit/skills/uscha-adr-refine/SKILL.md +161 -0
- package/uscha-kit/skills/uscha-characterize/SKILL.md +124 -0
- package/uscha-kit/skills/uscha-devloop/SKILL.md +490 -0
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +5602 -0
- package/uscha-kit/skills/uscha-discovery/SKILL.md +161 -0
- package/uscha-kit/skills/uscha-mirador/SKILL.md +145 -0
- package/uscha-kit/skills/uscha-mirador/mirador-render.py +112 -0
- package/uscha-kit/skills/uscha-mirador/mirador-watch.ps1 +22 -0
- package/uscha-kit/skills/uscha-mirador/mirador-watch.sh +26 -0
- package/uscha-kit/skills/uscha-mirador/mirador.template.html +586 -0
- package/uscha-kit/skills/uscha-mirador/telemetry-extract.py +130 -0
- package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +116 -0
- package/uscha-kit/skills/uscha-rubric/SKILL.md +79 -0
- package/uscha-kit/skills/uscha-sysdoc/SKILL.md +88 -0
- package/uscha-kit/templates/.gitattributes +6 -0
- package/uscha-kit/templates/CLAUDE.md +56 -0
- package/uscha-kit/templates/CONSTITUTION.md +149 -0
- package/uscha-kit/templates/RUBRIC.md +38 -0
- package/uscha-kit/templates/docs/adr/README.md +19 -0
- package/uscha-kit/templates/rubric-grader-prompt.md +63 -0
- package/uscha-kit/tests/smoke-engine.sh +1739 -0
- package/uscha-kit/uscha.config.json +181 -0
- package/uscha-kit/workbench-doctor.sh +45 -0
|
@@ -0,0 +1,1739 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# smoke-engine.sh — suite de smoke del motor qa_ledger.py contra un ledger SINTÉTICO.
|
|
3
|
+
# Valida el cableado de los fact gates (v1.3.0): log-gate, flag-blocker,
|
|
4
|
+
# resolve-escalation, UNMEASURED, convergencia per-tool, gate-check, golden-diff,
|
|
5
|
+
# spec-check estructural, simplicity floor, oscillation — el adapter python
|
|
6
|
+
# (v1.4.0): coverage Cobertura, junit envuelto, LOC, ruff/mypy, UNMEASURED python —
|
|
7
|
+
# el adapter node (v1.5.0): lcov, jest-junit, LOC ts, eslint/tsc, UNMEASURED node —
|
|
8
|
+
# el adapter go (v1.6.0): cover profile, gotestsum, LOC _test.go, golangci —
|
|
9
|
+
# los adapters rust/dotnet (v1.7.0): Cobertura reusada, clippy JSONL, SARIF —
|
|
10
|
+
# el adapter cpp (v1.8.0): gcovr Cobertura, ctest junit plano, clang-tidy —
|
|
11
|
+
# y los adapters gradle/swift (v1.9.0): JaCoCo/lcov/junit/checkstyle reusados
|
|
12
|
+
# en paths nuevos (detekt, swiftlint) + fixes de los reviews 1.7.0/1.8.0 —
|
|
13
|
+
# y acceptance trazable (v1.10.0): AC-n cierra por testcase medido, spec-check
|
|
14
|
+
# --acceptance como FACT estructural —
|
|
15
|
+
# y tests fuera del presupuesto de simplicity (v1.11.0): escribir tests no
|
|
16
|
+
# penaliza el gate (Topic 51) + edges 1.10.0 (falsos positivos, flaky) —
|
|
17
|
+
# y secret-scan en gate-check (v1.12.0): secretos agregados bloquean como hecho —
|
|
18
|
+
# y ledger atomico (v1.13.0): checksum de integridad + carga blindada —
|
|
19
|
+
# y plateau/stop-signal (v1.14.0): stall y candidato-a-PR como advisories —
|
|
20
|
+
# y golden scrub (v1.15.0): volatiles declarados enmascaran con masking visible —
|
|
21
|
+
# y regression-capture (v1.16.0): cierre sin test = narrado; escape-analysis
|
|
22
|
+
# obligatoria al resolver blockers — y procedencia de umbrales (v1.17.0):
|
|
23
|
+
# requerimiento (config) vs default del kit, etiquetado en cada gate —
|
|
24
|
+
# y phase (v1.18.0): FSM derivada del ledger, el estado se computa —
|
|
25
|
+
# y spikes (v1.19.0): rama spike/* jamas pasa el gate de PR —
|
|
26
|
+
# y doctor (v1.22.0): diagnostico de la instalacion, ledger corrupto = error —
|
|
27
|
+
# y rubric layer (v1.23.0): criterio cualitativo versionado, agnostico (T43 a mano).
|
|
28
|
+
#
|
|
29
|
+
# Uso: bash tests/smoke-engine.sh (desde la raíz del kit)
|
|
30
|
+
# Exit: 0 = todos los checks verdes · 1 = algún check falló
|
|
31
|
+
#
|
|
32
|
+
# Nota INV-GOLDEN-01: el path CLEAN de golden-diff NO se auto-testea — crear un
|
|
33
|
+
# fixture aprobado es un acto HUMANO incluso en tests. Se cubren NOT-RUN y DIVERGE.
|
|
34
|
+
|
|
35
|
+
set -u
|
|
36
|
+
# stdin SIEMPRE cerrado: spec-check (y cualquier subcomando futuro) lee stdin
|
|
37
|
+
# cuando viene redirigido — con un pipe abierto sin EOF la suite se cuelga
|
|
38
|
+
# infinito (paso en la vida real: exit 124 por timeout con commit encadenado).
|
|
39
|
+
exec < /dev/null
|
|
40
|
+
KIT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
41
|
+
ROOT="$(cd "$KIT/.." && pwd)"
|
|
42
|
+
QL="$KIT/.claude/skills/uscha-devloop/qa_ledger.py"
|
|
43
|
+
# probe FUNCIONAL: en Windows 'python3' puede ser un stub de Store que está en
|
|
44
|
+
# PATH pero no ejecuta — hay que probar --version, no solo command -v.
|
|
45
|
+
PY="${PYTHON:-}"
|
|
46
|
+
if [ -z "$PY" ]; then
|
|
47
|
+
for cand in python3 python py; do
|
|
48
|
+
if "$cand" --version >/dev/null 2>&1; then PY="$cand"; break; fi
|
|
49
|
+
done
|
|
50
|
+
fi
|
|
51
|
+
[ -n "$PY" ] || { echo "FAIL: no hay Python funcional en PATH"; exit 1; }
|
|
52
|
+
SB="$(mktemp -d 2>/dev/null || echo "${TMP:-/tmp}/smoke-$$")"; mkdir -p "$SB/repo-a" "$SB/repo-b" "$SB/repo-c" "$SB/repo-d" "$SB/repo-e" "$SB/repo-f" "$SB/repo-g" "$SB/repo-h" "$SB/repo-i" "$SB/repo-j"
|
|
53
|
+
cd "$SB"
|
|
54
|
+
|
|
55
|
+
PASS=0; FAIL=0
|
|
56
|
+
chk() { # $1 = descripción, $2 = exit esperado, $3.. = comando
|
|
57
|
+
local desc="$1" want="$2"; shift 2
|
|
58
|
+
"$@" >/dev/null 2>&1; local got=$?
|
|
59
|
+
if [ "$got" -eq "$want" ]; then PASS=$((PASS+1)); echo " ok $desc"
|
|
60
|
+
else FAIL=$((FAIL+1)); echo " FAIL $desc (exit $got, esperado $want)"; fi
|
|
61
|
+
}
|
|
62
|
+
run() { PYTHONIOENCODING=utf-8 "$PY" "$QL" "$@"; }
|
|
63
|
+
|
|
64
|
+
cat > uscha.config.json <<'EOF'
|
|
65
|
+
{ "version": "1.3.0",
|
|
66
|
+
"defaults": { "coverage_threshold": 60, "tools_per_cycle": 3,
|
|
67
|
+
"severity_gate": ["BLOCKER","CRITICAL","HIGH"],
|
|
68
|
+
"qa_tools_order": ["code-review","judgment-day","improve"],
|
|
69
|
+
"acceptance_file": "ACCEPTANCE.md" },
|
|
70
|
+
"repos": [ {"name":"repo-a","path":"repo-a","type":"maven"},
|
|
71
|
+
{"name":"repo-b","path":"repo-b","type":"flutter"},
|
|
72
|
+
{"name":"repo-c","path":"repo-c","type":"python"},
|
|
73
|
+
{"name":"repo-d","path":"repo-d","type":"node"},
|
|
74
|
+
{"name":"repo-e","path":"repo-e","type":"go"},
|
|
75
|
+
{"name":"repo-f","path":"repo-f","type":"rust"},
|
|
76
|
+
{"name":"repo-g","path":"repo-g","type":"dotnet"},
|
|
77
|
+
{"name":"repo-h","path":"repo-h","type":"cpp"},
|
|
78
|
+
{"name":"repo-i","path":"repo-i","type":"gradle"},
|
|
79
|
+
{"name":"repo-j","path":"repo-j","type":"swift"} ],
|
|
80
|
+
"integration": {"enabled": false} }
|
|
81
|
+
EOF
|
|
82
|
+
printf -- "# ACCEPTANCE\n\n- [x] criterio uno\n- [ ] criterio dos\n" > ACCEPTANCE.md
|
|
83
|
+
run init --config uscha.config.json >/dev/null || { echo "FAIL init"; exit 1; }
|
|
84
|
+
|
|
85
|
+
echo "== T1 readiness virgen: static UNMEASURED, no 1.0 por silencio =="
|
|
86
|
+
run readiness 2>/dev/null | grep -q "UNMEASURED" && { PASS=$((PASS+1)); echo " ok warning UNMEASURED presente"; } || { FAIL=$((FAIL+1)); echo " FAIL sin warning UNMEASURED"; }
|
|
87
|
+
|
|
88
|
+
echo "== T2 converged exige TODAS las tools de qa_tools_order =="
|
|
89
|
+
run log-step --repo repo-a --tool code-review --iteration 1 --tests-passed true >/dev/null
|
|
90
|
+
run log-step --repo repo-a --tool judgment-day --iteration 1 --tests-passed true >/dev/null
|
|
91
|
+
chk "falta 'improve' -> NOT converged" 1 run converged --repo repo-a
|
|
92
|
+
|
|
93
|
+
echo "== T3 log-gate fail bloquea convergencia; pass la limpia =="
|
|
94
|
+
run log-step --repo repo-a --tool improve --iteration 1 --tests-passed true >/dev/null
|
|
95
|
+
chk "ciclo completo limpio -> CONVERGED" 0 run converged --repo repo-a
|
|
96
|
+
run log-gate --repo repo-a --iteration 1 --kind golden-diff --verdict fail --note smoke >/dev/null
|
|
97
|
+
chk "fact gate rojo -> NOT converged" 1 run converged --repo repo-a
|
|
98
|
+
run log-gate --repo repo-a --iteration 2 --kind golden-diff --verdict pass >/dev/null
|
|
99
|
+
chk "fact gate pass -> CONVERGED de nuevo" 0 run converged --repo repo-a
|
|
100
|
+
|
|
101
|
+
echo "== T4 flag-blocker (constitution) bloquea hasta --resolve =="
|
|
102
|
+
run flag-blocker --repo repo-a --kind constitution --note "INV-X breached" >/dev/null
|
|
103
|
+
chk "blocker abierto -> NOT converged" 1 run converged --repo repo-a
|
|
104
|
+
chk "resolver SIN escape-analysis -> rechazado (Find Bugs Once)" 1 \
|
|
105
|
+
run flag-blocker --repo repo-a --kind constitution --resolve
|
|
106
|
+
run flag-blocker --repo repo-a --kind constitution --resolve \
|
|
107
|
+
--escape-analysis "hook nuevo + test que cubre INV-X" >/dev/null
|
|
108
|
+
chk "blocker resuelto (con escape analysis) -> CONVERGED" 0 run converged --repo repo-a
|
|
109
|
+
|
|
110
|
+
echo "== T5 escalate/resolve-escalation registrados =="
|
|
111
|
+
# el cap_reason solo se muestra cuando el cap MUERDE (score > techo); con score bajo
|
|
112
|
+
# lo observable es el REGISTRO: la escalación existe sin resolved_at y luego con él.
|
|
113
|
+
run escalate --repo repo-a --reason "smoke" >/dev/null
|
|
114
|
+
"$PY" -c "import json,sys; e=json.load(open('QA-LEDGER.json'))['escalations']; sys.exit(0 if any(not x.get('resolved_at') for x in e) else 1)" \
|
|
115
|
+
&& { PASS=$((PASS+1)); echo " ok escalación abierta registrada"; } || { FAIL=$((FAIL+1)); echo " FAIL escalación no registrada"; }
|
|
116
|
+
run resolve-escalation --repo repo-a >/dev/null
|
|
117
|
+
"$PY" -c "import json,sys; e=json.load(open('QA-LEDGER.json'))['escalations']; sys.exit(0 if all(x.get('resolved_at') for x in e) else 1)" \
|
|
118
|
+
&& { PASS=$((PASS+1)); echo " ok resolved_at registrado al resolver"; } || { FAIL=$((FAIL+1)); echo " FAIL resolved_at ausente"; }
|
|
119
|
+
|
|
120
|
+
echo "== T6 gate-check: borrado de archivo de test entero =="
|
|
121
|
+
printf -- "diff --git a/src/test/java/FooTest.java b/src/test/java/FooTest.java\n--- a/src/test/java/FooTest.java\n+++ /dev/null\n@@ -1,3 +0,0 @@\n-import org.junit.jupiter.api.Test;\n-@Test\n-void testX() { assertEquals(1,1); }\n" > del.diff
|
|
122
|
+
chk "delete de test file -> BLOCKER" 1 run gate-check --diff del.diff
|
|
123
|
+
|
|
124
|
+
echo "== T7 gate-check: threshold bajado cross-hunk + borrado sin re-add =="
|
|
125
|
+
printf -- "diff --git a/pom.xml b/pom.xml\n--- a/pom.xml\n+++ b/pom.xml\n@@ -10,1 +10,0 @@\n- <coverage-minimum>0.80</coverage-minimum>\n@@ -90,0 +90,1 @@\n+ <coverage-minimum>0.50</coverage-minimum>\n" > thr.diff
|
|
126
|
+
chk "lowered cross-hunk -> BLOCKER" 1 run gate-check --diff thr.diff
|
|
127
|
+
printf -- "diff --git a/pom.xml b/pom.xml\n--- a/pom.xml\n+++ b/pom.xml\n@@ -10,1 +10,0 @@\n- <coverage-minimum>0.80</coverage-minimum>\n@@ -55,0 +55,1 @@\n+ <coverage-minimum>0.80</coverage-minimum>\n" > move.diff
|
|
128
|
+
chk "mover threshold igual -> CLEAN" 0 run gate-check --diff move.diff
|
|
129
|
+
|
|
130
|
+
echo "== T7b gate-check: dependencia nueva = senal BLANDA (kit 1.30.0) =="
|
|
131
|
+
# la CONSTITUTION dice '0 deps nuevas sin aprobacion'; gate-check la hace visible.
|
|
132
|
+
printf -- 'diff --git a/package.json b/package.json\n--- a/package.json\n+++ b/package.json\n@@ -10,3 +10,4 @@\n "dependencies": {\n+ "sketchy-lib": "^2.1.0",\n "react": "^18.0.0"\n' > dep.diff
|
|
133
|
+
run gate-check --diff dep.diff --json 2>/dev/null | "$PY" -c "
|
|
134
|
+
import json, sys
|
|
135
|
+
d = json.load(sys.stdin)
|
|
136
|
+
ok = (d['verdict'] == 'REVIEW' and len(d['new_dependencies']) == 1
|
|
137
|
+
and 'sketchy-lib' in d['new_dependencies'][0])
|
|
138
|
+
sys.exit(0 if ok else 1)" \
|
|
139
|
+
&& { PASS=$((PASS+1)); echo " ok dep nueva -> REVIEW + listada en new_dependencies"; } \
|
|
140
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL dep nueva no detectada"; }
|
|
141
|
+
chk "dep nueva SIN --strict -> exit 0 (advisory)" 0 run gate-check --diff dep.diff
|
|
142
|
+
chk "dep nueva CON --strict -> exit 1 (gatea la senal blanda)" 1 run gate-check --diff dep.diff --strict
|
|
143
|
+
# un diff de codigo normal NO debe flaguear dep (sin falsos positivos)
|
|
144
|
+
printf -- 'diff --git a/src/mod.py b/src/mod.py\n--- a/src/mod.py\n+++ b/src/mod.py\n@@ -1,2 +1,3 @@\n def f(x):\n+ return x + 1\n' > nodep.diff
|
|
145
|
+
run gate-check --diff nodep.diff --json 2>/dev/null | "$PY" -c "
|
|
146
|
+
import json, sys
|
|
147
|
+
sys.exit(0 if json.load(sys.stdin)['new_dependencies'] == [] else 1)" \
|
|
148
|
+
&& { PASS=$((PASS+1)); echo " ok diff de codigo normal no flaguea dep (sin falso positivo)"; } \
|
|
149
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL falso positivo de dep en codigo normal"; }
|
|
150
|
+
|
|
151
|
+
echo "== T8 golden-diff: NOT-RUN y DIVERGE =="
|
|
152
|
+
mkdir -p g && ( cd g && chk "cero fixtures -> NOT-RUN exit 2" 2 run golden-diff )
|
|
153
|
+
( cd g && printf "x" > f.received.txt && chk "fixture sin aprobar -> DIVERGE exit 1" 1 run golden-diff )
|
|
154
|
+
|
|
155
|
+
echo "== T9 spec-check: estructural bloquea, completo OK =="
|
|
156
|
+
printf -- "# SPEC\n\n## Acceptance\n\n- [ ] when a then shall b exactly 80.00\n" > s1.md
|
|
157
|
+
chk "sin out-of-scope -> exit 1" 1 run spec-check --spec s1.md
|
|
158
|
+
printf -- "# SPEC\n\n## Out of scope\n\n- x\n\n## Acceptance\n\n- [ ] when a then shall b exactly 80.00\n" > s2.md
|
|
159
|
+
chk "spec completa -> exit 0" 0 run spec-check --spec s2.md
|
|
160
|
+
|
|
161
|
+
echo "== T10 simplicity: floor de dims pesadas (1.9x -> OVERBUILT) =="
|
|
162
|
+
{ printf -- "diff --git a/src/A.java b/src/A.java\n--- a/src/A.java\n+++ b/src/A.java\n@@ -1,0 +1,190 @@\n"; for i in $(seq 1 190); do printf -- "+int x%d = %d;\n" "$i" "$i"; done; } > big.diff
|
|
163
|
+
chk "diff 1.9x budget -> OVERBUILT exit 1" 1 run simplicity-check --diff big.diff --max-lines-added 100 --max-net-lines 999 --max-files-changed 20 --max-hunk-added 999
|
|
164
|
+
|
|
165
|
+
echo "== T11 oscillation Jaccard (a,b -> c -> a,b) =="
|
|
166
|
+
run log-step --repo repo-b --tool code-review --iteration 1 --fingerprint "a,b" >/dev/null
|
|
167
|
+
run log-step --repo repo-b --tool code-review --iteration 2 --fingerprint "c" >/dev/null
|
|
168
|
+
run log-step --repo repo-b --tool code-review --iteration 3 --fingerprint "a,b" >/dev/null
|
|
169
|
+
chk "set repetido -> OSCILLATING exit 1" 1 run oscillation --repo repo-b --tool code-review
|
|
170
|
+
|
|
171
|
+
echo "== T12 converged: snapshot rojo MEDIDO veta verde narrado =="
|
|
172
|
+
mkdir -p repo-a/target/surefire-reports
|
|
173
|
+
printf '<testsuite name="F" tests="6" failures="2" errors="0" skipped="0"/>\n' > repo-a/target/surefire-reports/TEST-F.xml
|
|
174
|
+
run snapshot --repo repo-a >/dev/null
|
|
175
|
+
chk "snapshot rojo -> NOT converged" 1 run converged --repo repo-a
|
|
176
|
+
|
|
177
|
+
echo "== T13 adapter python: coverage Cobertura + junit ENVUELTO + LOC =="
|
|
178
|
+
mkdir -p repo-c/src/pkg repo-c/tests repo-c/reports
|
|
179
|
+
printf 'def f():\n return 1\nX = 2\n' > repo-c/src/pkg/mod.py
|
|
180
|
+
printf 'from src.pkg import mod\ndef test_f(): assert mod.f() == 1\n' > repo-c/tests/test_mod.py
|
|
181
|
+
cat > repo-c/coverage.xml <<'EOF'
|
|
182
|
+
<?xml version="1.0"?>
|
|
183
|
+
<coverage lines-valid="10" lines-covered="8" line-rate="0.8" version="7.4"></coverage>
|
|
184
|
+
EOF
|
|
185
|
+
cat > repo-c/reports/junit.xml <<'EOF'
|
|
186
|
+
<?xml version="1.0"?>
|
|
187
|
+
<testsuites><testsuite name="pytest" tests="5" failures="0" errors="0" skipped="1"/></testsuites>
|
|
188
|
+
EOF
|
|
189
|
+
SNAP=$(run snapshot --repo repo-c 2>&1)
|
|
190
|
+
echo "$SNAP" | grep -q "coverage=80.0%" && { PASS=$((PASS+1)); echo " ok coverage Cobertura 8/10 -> 80.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage python ($SNAP)"; }
|
|
191
|
+
echo "$SNAP" | grep -q "tests=5" && { PASS=$((PASS+1)); echo " ok junit ENVUELTO (testsuites>testsuite) -> 5 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count python ($SNAP)"; }
|
|
192
|
+
echo "$SNAP" | grep -q "prod_loc=3, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC prod=3 / test=2 bien clasificado"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC python ($SNAP)"; }
|
|
193
|
+
|
|
194
|
+
echo "== T14 python UNMEASURED pre-ingest / medido post-ingest =="
|
|
195
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-c" \
|
|
196
|
+
&& { PASS=$((PASS+1)); echo " ok repo-c UNMEASURED antes del ingest"; } \
|
|
197
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL repo-c no aparece como UNMEASURED"; }
|
|
198
|
+
cat > repo-c/reports/ruff.json <<'EOF'
|
|
199
|
+
[{"code":"S101","filename":"src/pkg/mod.py","location":{"row":3}},
|
|
200
|
+
{"code":"E501","filename":"src/pkg/mod.py","location":{"row":1}},
|
|
201
|
+
{"code":null,"filename":"src/pkg/mod.py","location":{"row":9}}]
|
|
202
|
+
EOF
|
|
203
|
+
printf 'src/pkg/mod.py:2: error: Incompatible return value type (got "int", expected "str") [return-value]\n' > repo-c/reports/mypy.txt
|
|
204
|
+
ING=$(run ingest-gate --repo repo-c --iteration 1 2>&1)
|
|
205
|
+
echo "$ING" | grep -q "repo-c/ruff: reported=3 gated=2" && { PASS=$((PASS+1)); echo " ok ruff: 3 findings, 2 gateados (S101 + code:null syntax = HIGH; E501 = LOW)"; } || { FAIL=$((FAIL+1)); echo " FAIL ruff ($ING)"; }
|
|
206
|
+
echo "$ING" | grep -q "repo-c/mypy: reported=1 gated=1" && { PASS=$((PASS+1)); echo " ok mypy: error -> HIGH gateado"; } || { FAIL=$((FAIL+1)); echo " FAIL mypy ($ING)"; }
|
|
207
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-c" \
|
|
208
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL repo-c sigue UNMEASURED tras ingest"; } \
|
|
209
|
+
|| { PASS=$((PASS+1)); echo " ok repo-c ya no es UNMEASURED tras ingest"; }
|
|
210
|
+
|
|
211
|
+
echo "== T15 adapter node: lcov + junit envuelto + LOC ts =="
|
|
212
|
+
mkdir -p repo-d/src repo-d/reports repo-d/coverage
|
|
213
|
+
printf 'export function f(): number {\n return 1;\n}\nexport const X = 2;\n' > repo-d/src/app.ts
|
|
214
|
+
printf 'import { f } from "./app";\ntest("f", () => expect(f()).toBe(1));\n' > repo-d/src/app.test.ts
|
|
215
|
+
printf 'SF:src/app.ts\nLF:10\nLH:9\nend_of_record\n' > repo-d/coverage/lcov.info
|
|
216
|
+
cat > repo-d/reports/junit.xml <<'EOF'
|
|
217
|
+
<?xml version="1.0"?>
|
|
218
|
+
<testsuites><testsuite name="jest" tests="7" failures="0" errors="0" skipped="0"/></testsuites>
|
|
219
|
+
EOF
|
|
220
|
+
SNAPD=$(run snapshot --repo repo-d 2>&1)
|
|
221
|
+
echo "$SNAPD" | grep -q "coverage=90.0%" && { PASS=$((PASS+1)); echo " ok coverage lcov 9/10 -> 90.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage node ($SNAPD)"; }
|
|
222
|
+
echo "$SNAPD" | grep -q "tests=7" && { PASS=$((PASS+1)); echo " ok junit envuelto (jest-junit) -> 7 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count node ($SNAPD)"; }
|
|
223
|
+
echo "$SNAPD" | grep -q "prod_loc=4, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC prod=4 (.ts) / test=2 (.test.ts) bien clasificado"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC node ($SNAPD)"; }
|
|
224
|
+
|
|
225
|
+
echo "== T16 node UNMEASURED pre-ingest / eslint+tsc post-ingest =="
|
|
226
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-d" \
|
|
227
|
+
&& { PASS=$((PASS+1)); echo " ok repo-d UNMEASURED antes del ingest"; } \
|
|
228
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL repo-d no aparece como UNMEASURED"; }
|
|
229
|
+
cat > repo-d/reports/eslint.json <<'EOF'
|
|
230
|
+
[{"filePath":"src/app.ts","messages":[
|
|
231
|
+
{"ruleId":"security/detect-eval-with-expression","severity":1,"line":2},
|
|
232
|
+
{"ruleId":"prefer-const","severity":1,"line":4},
|
|
233
|
+
{"ruleId":null,"severity":1,"line":5},
|
|
234
|
+
{"ruleId":null,"severity":2,"line":9,"fatal":true}]}]
|
|
235
|
+
EOF
|
|
236
|
+
printf 'src/app.ts(2,3): error TS2322: Type mismatch.\nerror TS18003: No inputs were found in config file.\n' > repo-d/reports/tsc.txt
|
|
237
|
+
INGD=$(run ingest-gate --repo repo-d --iteration 1 2>&1)
|
|
238
|
+
echo "$INGD" | grep -q "repo-d/eslint: reported=4 gated=2" && { PASS=$((PASS+1)); echo " ok eslint: security floor + fatal = HIGH; null NO-fatal (ESLint 9) = MEDIUM, no bloquea"; } || { FAIL=$((FAIL+1)); echo " FAIL eslint ($INGD)"; }
|
|
239
|
+
echo "$INGD" | grep -q "repo-d/tsc: reported=2 gated=2" && { PASS=$((PASS+1)); echo " ok tsc: error con archivo + error GLOBAL sin archivo (tsconfig roto) = HIGH"; } || { FAIL=$((FAIL+1)); echo " FAIL tsc ($INGD)"; }
|
|
240
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-d" \
|
|
241
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL repo-d sigue UNMEASURED tras ingest"; } \
|
|
242
|
+
|| { PASS=$((PASS+1)); echo " ok repo-d ya no es UNMEASURED tras ingest"; }
|
|
243
|
+
|
|
244
|
+
echo "== T17 adapter go: cover profile nativo + gotestsum junit + LOC _test.go =="
|
|
245
|
+
mkdir -p repo-e/pkg repo-e/reports
|
|
246
|
+
printf 'package pkg\nfunc F() int {\nreturn 1\n}\n' > repo-e/pkg/mod.go
|
|
247
|
+
printf 'package pkg\nfunc TestF(t *T) {}\n' > repo-e/pkg/mod_test.go
|
|
248
|
+
cat > repo-e/coverage.out <<'EOF'
|
|
249
|
+
mode: set
|
|
250
|
+
example.com/m/pkg/mod.go:2.15,4.2 3 1
|
|
251
|
+
example.com/m/pkg/mod.go:6.2,8.3 2 0
|
|
252
|
+
EOF
|
|
253
|
+
cat > repo-e/reports/junit.xml <<'EOF'
|
|
254
|
+
<?xml version="1.0"?>
|
|
255
|
+
<testsuites><testsuite name="gotestsum" tests="4" failures="0" errors="0" skipped="0"/></testsuites>
|
|
256
|
+
EOF
|
|
257
|
+
SNAPE=$(run snapshot --repo repo-e 2>&1)
|
|
258
|
+
echo "$SNAPE" | grep -q "coverage=60.0%" && { PASS=$((PASS+1)); echo " ok cover profile 3/5 stmts -> 60.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage go ($SNAPE)"; }
|
|
259
|
+
echo "$SNAPE" | grep -q "tests=4" && { PASS=$((PASS+1)); echo " ok gotestsum junit envuelto -> 4 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count go ($SNAPE)"; }
|
|
260
|
+
echo "$SNAPE" | grep -q "prod_loc=4, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC prod=4 (mod.go) / test=2 (_test.go junto al codigo)"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC go ($SNAPE)"; }
|
|
261
|
+
|
|
262
|
+
echo "== T18 go UNMEASURED pre-ingest / golangci (checkstyle reusado) post-ingest =="
|
|
263
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-e" \
|
|
264
|
+
&& { PASS=$((PASS+1)); echo " ok repo-e UNMEASURED antes del ingest"; } \
|
|
265
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL repo-e no aparece como UNMEASURED"; }
|
|
266
|
+
cat > repo-e/reports/golangci.xml <<'EOF'
|
|
267
|
+
<?xml version="1.0"?>
|
|
268
|
+
<checkstyle version="5.0">
|
|
269
|
+
<file name="pkg/mod.go">
|
|
270
|
+
<error line="3" severity="error" message="G104: unhandled error" source="gosec"/>
|
|
271
|
+
<error line="7" severity="warning" message="var x is unused" source="unused"/>
|
|
272
|
+
</file>
|
|
273
|
+
</checkstyle>
|
|
274
|
+
EOF
|
|
275
|
+
INGE=$(run ingest-gate --repo repo-e --iteration 1 2>&1)
|
|
276
|
+
echo "$INGE" | grep -q "repo-e/golangci: reported=2 gated=1" && { PASS=$((PASS+1)); echo " ok golangci via parse_checkstyle: error=HIGH gateado, warning=MEDIUM"; } || { FAIL=$((FAIL+1)); echo " FAIL golangci ($INGE)"; }
|
|
277
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-e" \
|
|
278
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL repo-e sigue UNMEASURED tras ingest"; } \
|
|
279
|
+
|| { PASS=$((PASS+1)); echo " ok repo-e ya no es UNMEASURED tras ingest"; }
|
|
280
|
+
|
|
281
|
+
echo "== T19 adapter rust: Cobertura (cargo llvm-cov) + nextest junit + LOC tests/ =="
|
|
282
|
+
mkdir -p repo-f/src repo-f/tests repo-f/reports
|
|
283
|
+
printf 'pub fn f() -> i32 {\n 1\n}\n' > repo-f/src/lib.rs
|
|
284
|
+
printf 'use core_lib::f;\n#[test] fn it_works() { assert_eq!(f(), 1); }\n' > repo-f/tests/it_test.rs
|
|
285
|
+
cat > repo-f/reports/coverage.xml <<'EOF'
|
|
286
|
+
<?xml version="1.0"?>
|
|
287
|
+
<coverage lines-valid="10" lines-covered="7" line-rate="0.7"></coverage>
|
|
288
|
+
EOF
|
|
289
|
+
cat > repo-f/reports/junit.xml <<'EOF'
|
|
290
|
+
<?xml version="1.0"?>
|
|
291
|
+
<testsuites><testsuite name="nextest" tests="3" failures="0" errors="0" skipped="0"/></testsuites>
|
|
292
|
+
EOF
|
|
293
|
+
SNAPF=$(run snapshot --repo repo-f 2>&1)
|
|
294
|
+
echo "$SNAPF" | grep -q "coverage=70.0%" && { PASS=$((PASS+1)); echo " ok Cobertura reusada (cargo llvm-cov) 7/10 -> 70.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage rust ($SNAPF)"; }
|
|
295
|
+
echo "$SNAPF" | grep -q "tests=3" && { PASS=$((PASS+1)); echo " ok nextest junit -> 3 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count rust ($SNAPF)"; }
|
|
296
|
+
echo "$SNAPF" | grep -q "prod_loc=3, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC prod=3 (src/) / test=2 (tests/)"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC rust ($SNAPF)"; }
|
|
297
|
+
|
|
298
|
+
echo "== T20 rust: clippy JSONL (error/warning/compile-error/summary/dup) =="
|
|
299
|
+
# el '1 warning emitted' de rustc es un diagnostico REAL (level warning, code
|
|
300
|
+
# null, spans []) — sin el skip por span, cada corrida con warnings crece un
|
|
301
|
+
# HIGH fantasma y el gate no converge jamas. El duplicado (lib + test target)
|
|
302
|
+
# debe dedupearse por finding ID.
|
|
303
|
+
cat > repo-f/reports/clippy.json <<'EOF'
|
|
304
|
+
{"reason":"compiler-message","message":{"level":"error","code":{"code":"clippy::unwrap_used"},"spans":[{"file_name":"src/lib.rs","line_start":2,"is_primary":true}]}}
|
|
305
|
+
{"reason":"compiler-message","message":{"level":"warning","code":{"code":"clippy::needless_return"},"spans":[{"file_name":"src/lib.rs","line_start":3,"is_primary":true}]}}
|
|
306
|
+
{"reason":"compiler-message","message":{"level":"warning","code":{"code":"clippy::needless_return"},"spans":[{"file_name":"src/lib.rs","line_start":3,"is_primary":true}]}}
|
|
307
|
+
{"reason":"compiler-message","message":{"level":"error","code":null,"spans":[{"file_name":"src/lib.rs","line_start":1,"is_primary":true}]}}
|
|
308
|
+
{"reason":"compiler-message","message":{"level":"warning","code":null,"spans":[],"message":"1 warning emitted"}}
|
|
309
|
+
{"reason":"compiler-message","message":{"level":"error","code":null,"spans":[],"message":"aborting due to 1 previous error"}}
|
|
310
|
+
{"reason":"build-finished","success":false}
|
|
311
|
+
EOF
|
|
312
|
+
INGF=$(run ingest-gate --repo repo-f --iteration 1 2>&1)
|
|
313
|
+
echo "$INGF" | grep -q "repo-f/clippy: reported=3 gated=2" && { PASS=$((PASS+1)); echo " ok clippy: 3 reales (summaries sin span NO cuentan, dup dedupeado)"; } || { FAIL=$((FAIL+1)); echo " FAIL clippy ($INGF)"; }
|
|
314
|
+
|
|
315
|
+
echo "== T21 adapter dotnet: Cobertura (coverlet) + junit logger + LOC .Tests =="
|
|
316
|
+
mkdir -p repo-g/src repo-g/Svc.Tests repo-g/reports
|
|
317
|
+
printf 'namespace Svc;\npublic class Api {\npublic int F() => 1;\n}\n' > repo-g/src/Api.cs
|
|
318
|
+
printf 'namespace Svc.Tests;\npublic class ApiTests { }\n' > repo-g/Svc.Tests/ApiTests.cs
|
|
319
|
+
cat > repo-g/reports/coverage.xml <<'EOF'
|
|
320
|
+
<?xml version="1.0"?>
|
|
321
|
+
<coverage lines-valid="12" lines-covered="9" line-rate="0.75"></coverage>
|
|
322
|
+
EOF
|
|
323
|
+
cat > repo-g/reports/junit.xml <<'EOF'
|
|
324
|
+
<?xml version="1.0"?>
|
|
325
|
+
<testsuites><testsuite name="dotnet" tests="6" failures="0" errors="0" skipped="0"/></testsuites>
|
|
326
|
+
EOF
|
|
327
|
+
SNAPG=$(run snapshot --repo repo-g 2>&1)
|
|
328
|
+
echo "$SNAPG" | grep -q "coverage=75.0%" && { PASS=$((PASS+1)); echo " ok Cobertura reusada (coverlet) 9/12 -> 75.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage dotnet ($SNAPG)"; }
|
|
329
|
+
echo "$SNAPG" | grep -q "tests=6" && { PASS=$((PASS+1)); echo " ok junit logger -> 6 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count dotnet ($SNAPG)"; }
|
|
330
|
+
echo "$SNAPG" | grep -q "prod_loc=4, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC prod=4 (src/) / test=2 (Svc.Tests/)"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC dotnet ($SNAPG)"; }
|
|
331
|
+
|
|
332
|
+
echo "== T22 dotnet: SARIF (Roslyn ErrorLog) error/warning/note =="
|
|
333
|
+
cat > repo-g/reports/analysis.sarif <<'EOF'
|
|
334
|
+
{"version":"2.1.0","runs":[{"tool":{"driver":{"name":"Roslyn"}},"results":[
|
|
335
|
+
{"ruleId":"CA2100","level":"error","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Api.cs"},"region":{"startLine":3}}}]},
|
|
336
|
+
{"ruleId":"CA1822","level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Api.cs"},"region":{"startLine":2}}}]},
|
|
337
|
+
{"ruleId":"IDE0005","level":"note","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Api.cs"},"region":{"startLine":1}}}]}]}]}
|
|
338
|
+
EOF
|
|
339
|
+
INGG=$(run ingest-gate --repo repo-g --iteration 1 2>&1)
|
|
340
|
+
echo "$INGG" | grep -q "repo-g/roslyn: reported=3 gated=1" && { PASS=$((PASS+1)); echo " ok SARIF: error=HIGH gateado; warning=MEDIUM; note=INFO"; } || { FAIL=$((FAIL+1)); echo " FAIL sarif ($INGG)"; }
|
|
341
|
+
|
|
342
|
+
echo "== T23 adapter cpp: gcovr Cobertura + ctest junit (root PLANO) + LOC tests/ =="
|
|
343
|
+
mkdir -p repo-h/src repo-h/tests repo-h/reports
|
|
344
|
+
printf '#include "core.h"\nint f() {\nreturn 1;\n}\n' > repo-h/src/core.cpp
|
|
345
|
+
printf '#include "core.h"\nTEST(Core, F) { EXPECT_EQ(f(), 1); }\n' > repo-h/tests/core_test.cpp
|
|
346
|
+
cat > repo-h/reports/coverage.xml <<'EOF'
|
|
347
|
+
<?xml version="1.0"?>
|
|
348
|
+
<coverage lines-valid="8" lines-covered="6" line-rate="0.75"></coverage>
|
|
349
|
+
EOF
|
|
350
|
+
cat > repo-h/reports/junit.xml <<'EOF'
|
|
351
|
+
<?xml version="1.0"?>
|
|
352
|
+
<testsuite name="ctest" tests="5" failures="0" errors="0" skipped="0"/>
|
|
353
|
+
EOF
|
|
354
|
+
SNAPH=$(run snapshot --repo repo-h 2>&1)
|
|
355
|
+
echo "$SNAPH" | grep -q "coverage=75.0%" && { PASS=$((PASS+1)); echo " ok gcovr Cobertura 6/8 -> 75.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage cpp ($SNAPH)"; }
|
|
356
|
+
echo "$SNAPH" | grep -q "tests=5" && { PASS=$((PASS+1)); echo " ok ctest junit root PLANO (testsuite) -> 5 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count cpp ($SNAPH)"; }
|
|
357
|
+
echo "$SNAPH" | grep -q "prod_loc=4, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC prod=4 (src/) / test=2 (tests/ + _test.cpp)"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC cpp ($SNAPH)"; }
|
|
358
|
+
|
|
359
|
+
echo "== T24 cpp: clang-tidy (error/warning/cert floor/.tpp) =="
|
|
360
|
+
cat > repo-h/reports/clang-tidy.txt <<'EOF'
|
|
361
|
+
src/core.cpp:3:1: error: use of undeclared identifier 'x' [clang-diagnostic-error]
|
|
362
|
+
src/core.cpp:2:5: warning: function 'f' should be marked const [readability-make-member-function-const]
|
|
363
|
+
src/core.cpp:3:8: warning: calling 'system' uses a command processor [cert-env33-c]
|
|
364
|
+
src/impl.tpp:4:2: warning: repeated branch body [bugprone-branch-clone]
|
|
365
|
+
2 warnings generated.
|
|
366
|
+
EOF
|
|
367
|
+
INGH=$(run ingest-gate --repo repo-h --iteration 1 2>&1)
|
|
368
|
+
echo "$INGH" | grep -q "repo-h/clang-tidy: reported=4 gated=2" && { PASS=$((PASS+1)); echo " ok clang-tidy: error+cert floor=HIGH; readability+.tpp=MEDIUM; ruido stderr ignorado"; } || { FAIL=$((FAIL+1)); echo " FAIL clang-tidy ($INGH)"; }
|
|
369
|
+
|
|
370
|
+
echo "== T25 fixes 1.7.0: junit root-max (gotestsum) + go dedupe + backtest.cpp prod =="
|
|
371
|
+
# (a) gotestsum reporta errors solo en el ROOT <testsuites> — el max(root, hijos)
|
|
372
|
+
# tiene que leer los atributos del root, no solo sumar hijos.
|
|
373
|
+
cat > repo-e/reports/junit.xml <<'EOF'
|
|
374
|
+
<?xml version="1.0"?>
|
|
375
|
+
<testsuites tests="9" failures="0" errors="1" skipped="0"><testsuite name="gotestsum" tests="4" failures="0" errors="0" skipped="0"/></testsuites>
|
|
376
|
+
EOF
|
|
377
|
+
SNAPE2=$(run snapshot --repo repo-e 2>&1)
|
|
378
|
+
echo "$SNAPE2" | grep -q "tests=9" && { PASS=$((PASS+1)); echo " ok junit root-attrs mandan: tests=9 (hijos sumaban 4)"; } || { FAIL=$((FAIL+1)); echo " FAIL junit root-max ($SNAPE2)"; }
|
|
379
|
+
# (b) -coverpkg repite bloques entre targets: dedupe por bloque con max(hits).
|
|
380
|
+
# El bloque de 2 stmts (0 hits) reaparece con 1 hit -> 5/5 stmts = 100%.
|
|
381
|
+
printf 'example.com/m/pkg/mod.go:6.2,8.3 2 1\n' >> repo-e/coverage.out
|
|
382
|
+
SNAPE3=$(run snapshot --repo repo-e 2>&1)
|
|
383
|
+
echo "$SNAPE3" | grep -q "coverage=100.0%" && { PASS=$((PASS+1)); echo " ok cover profile dedupeado por bloque con max(hits) -> 100.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL go dedupe ($SNAPE3)"; }
|
|
384
|
+
# (c) sufijo bare 'test.cpp' NO debe tragar backtest.cpp como test LOC.
|
|
385
|
+
printf 'int backtest() {\nreturn 2;\n}\n' > repo-h/src/backtest.cpp
|
|
386
|
+
SNAPH2=$(run snapshot --repo repo-h 2>&1)
|
|
387
|
+
echo "$SNAPH2" | grep -q "prod_loc=7, test_loc=2" && { PASS=$((PASS+1)); echo " ok backtest.cpp cuenta como PROD (CamelCase Test.cpp es el patron de test)"; } || { FAIL=$((FAIL+1)); echo " FAIL backtest.cpp ($SNAPH2)"; }
|
|
388
|
+
|
|
389
|
+
echo "== T26 adapter gradle: JaCoCo en paths gradle + test-results + LOC source sets =="
|
|
390
|
+
mkdir -p repo-i/src/main/kotlin repo-i/src/test/kotlin repo-i/src/integrationTest/kotlin repo-i/build/reports/jacoco/test repo-i/build/test-results/test repo-i/build/reports/detekt
|
|
391
|
+
printf 'package app\nfun f(): Int {\nreturn 1\n}\n' > repo-i/src/main/kotlin/App.kt
|
|
392
|
+
printf 'package app\nclass AppTest { }\n' > repo-i/src/test/kotlin/AppTest.kt
|
|
393
|
+
printf 'package app\nclass AppIT { }\n' > repo-i/src/integrationTest/kotlin/AppIT.kt
|
|
394
|
+
cat > repo-i/build/reports/jacoco/test/jacocoTestReport.xml <<'EOF'
|
|
395
|
+
<?xml version="1.0"?>
|
|
396
|
+
<report name="jvm-service"><counter type="LINE" missed="3" covered="9"/></report>
|
|
397
|
+
EOF
|
|
398
|
+
cat > repo-i/build/test-results/test/TEST-app.AppTest.xml <<'EOF'
|
|
399
|
+
<?xml version="1.0"?>
|
|
400
|
+
<testsuite name="app.AppTest" tests="8" failures="0" errors="0" skipped="0"/>
|
|
401
|
+
EOF
|
|
402
|
+
SNAPI=$(run snapshot --repo repo-i 2>&1)
|
|
403
|
+
echo "$SNAPI" | grep -q "coverage=75.0%" && { PASS=$((PASS+1)); echo " ok JaCoCo en build/reports/jacoco 9/12 -> 75.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage gradle ($SNAPI)"; }
|
|
404
|
+
echo "$SNAPI" | grep -q "tests=8" && { PASS=$((PASS+1)); echo " ok build/test-results TEST-*.xml -> 8 tests"; } || { FAIL=$((FAIL+1)); echo " FAIL test count gradle ($SNAPI)"; }
|
|
405
|
+
echo "$SNAPI" | grep -q "prod_loc=4, test_loc=4" && { PASS=$((PASS+1)); echo " ok LOC src/main=4 prod / src/test + src/integrationTest=4 test (source sets custom)"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC gradle ($SNAPI)"; }
|
|
406
|
+
|
|
407
|
+
echo "== T27 gradle: detekt (checkstyle reusado, paths ABSOLUTOS como el default real) =="
|
|
408
|
+
ABSI="$("$PY" -c "import os;print(os.path.abspath('repo-i/src/main/kotlin/App.kt'))")"
|
|
409
|
+
cat > repo-i/build/reports/detekt/detekt.xml <<EOF
|
|
410
|
+
<?xml version="1.0"?>
|
|
411
|
+
<checkstyle version="4.3">
|
|
412
|
+
<file name="$ABSI">
|
|
413
|
+
<error line="2" severity="error" message="ForbiddenCall" source="detekt.ForbiddenCall"/>
|
|
414
|
+
<error line="3" severity="warning" message="MagicNumber" source="detekt.MagicNumber"/>
|
|
415
|
+
</file>
|
|
416
|
+
</checkstyle>
|
|
417
|
+
EOF
|
|
418
|
+
INGI=$(run ingest-gate --repo repo-i --iteration 1 2>&1)
|
|
419
|
+
echo "$INGI" | grep -q "repo-i/detekt: reported=2 gated=1" && { PASS=$((PASS+1)); echo " ok detekt via parse_checkstyle: error=HIGH gateado, warning=MEDIUM"; } || { FAIL=$((FAIL+1)); echo " FAIL detekt ($INGI)"; }
|
|
420
|
+
"$PY" -c "
|
|
421
|
+
import json, sys
|
|
422
|
+
node = json.load(open('QA-LEDGER.json'))['repos']['repo-i']
|
|
423
|
+
ids = [i for s in node['iterations'] if s.get('finding_ids') for i in s['finding_ids']]
|
|
424
|
+
ok = any(x.startswith('detekt:') and 'src/main/kotlin/App.kt' in x.replace(chr(92), '/') and not x.replace(chr(92), '/').count(':/') for x in ids)
|
|
425
|
+
sys.exit(0 if ok else 1)" \
|
|
426
|
+
&& { PASS=$((PASS+1)); echo " ok IDs detekt repo-relativos (path absoluto relativizado)"; } \
|
|
427
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL IDs detekt no relativizados"; }
|
|
428
|
+
run readiness 2>/dev/null | grep "NEVER ran" | grep -q "repo-i" \
|
|
429
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL repo-i sigue UNMEASURED tras ingest"; } \
|
|
430
|
+
|| { PASS=$((PASS+1)); echo " ok repo-i ya no es UNMEASURED tras ingest"; }
|
|
431
|
+
|
|
432
|
+
echo "== T28 adapter swift: lcov + xunit + LOC Sources/Tests + swiftlint =="
|
|
433
|
+
mkdir -p repo-j/Sources/Kit repo-j/Tests/KitTests repo-j/coverage repo-j/reports
|
|
434
|
+
printf 'public func f() -> Int {\nreturn 1\n}\n' > repo-j/Sources/Kit/Kit.swift
|
|
435
|
+
printf 'import XCTest\nfinal class KitTests: XCTestCase { }\n' > repo-j/Tests/KitTests/KitTests.swift
|
|
436
|
+
printf 'SF:Sources/Kit/Kit.swift\nLF:16\nLH:12\nend_of_record\n' > repo-j/coverage/lcov.info
|
|
437
|
+
cat > repo-j/reports/junit.xml <<'EOF'
|
|
438
|
+
<?xml version="1.0"?>
|
|
439
|
+
<testsuites><testsuite name="KitTests" tests="7" failures="0" errors="0" skipped="0"/></testsuites>
|
|
440
|
+
EOF
|
|
441
|
+
# Swift 6 / Swift Testing: --xunit-output escribe un SEGUNDO archivo con los
|
|
442
|
+
# resultados de Swift Testing — el engine debe SUMAR ambos (sets disjuntos);
|
|
443
|
+
# si lo ignorara, este failure real seria invisible (fail-open).
|
|
444
|
+
cat > repo-j/reports/junit-swift-testing.xml <<'EOF'
|
|
445
|
+
<?xml version="1.0"?>
|
|
446
|
+
<testsuites><testsuite name="SwiftTesting" tests="2" failures="1" errors="0" skipped="0"/></testsuites>
|
|
447
|
+
EOF
|
|
448
|
+
SNAPJ=$(run snapshot --repo repo-j 2>&1)
|
|
449
|
+
echo "$SNAPJ" | grep -q "coverage=75.0%" && { PASS=$((PASS+1)); echo " ok lcov reusado (llvm-cov export) 12/16 -> 75.0%"; } || { FAIL=$((FAIL+1)); echo " FAIL coverage swift ($SNAPJ)"; }
|
|
450
|
+
echo "$SNAPJ" | grep -q "tests=9" && { PASS=$((PASS+1)); echo " ok XCTest (7) + Swift Testing (2) SUMADOS -> 9 tests (failure real visible)"; } || { FAIL=$((FAIL+1)); echo " FAIL test count swift dual-file ($SNAPJ)"; }
|
|
451
|
+
echo "$SNAPJ" | grep -q "prod_loc=3, test_loc=2" && { PASS=$((PASS+1)); echo " ok LOC Sources/=3 / Tests/=2 (convencion SwiftPM)"; } || { FAIL=$((FAIL+1)); echo " FAIL LOC swift ($SNAPJ)"; }
|
|
452
|
+
ABSJ="$("$PY" -c "import os;print(os.path.abspath('repo-j/Sources/Kit/Kit.swift'))")"
|
|
453
|
+
cat > repo-j/reports/swiftlint.xml <<EOF
|
|
454
|
+
<?xml version="1.0"?>
|
|
455
|
+
<checkstyle version="4.3">
|
|
456
|
+
<file name="$ABSJ">
|
|
457
|
+
<error line="1" severity="error" message="Force Cast Violation" source="swiftlint.force_cast"/>
|
|
458
|
+
<error line="2" severity="warning" message="Line Length Violation" source="swiftlint.line_length"/>
|
|
459
|
+
</file>
|
|
460
|
+
</checkstyle>
|
|
461
|
+
EOF
|
|
462
|
+
INGJ=$(run ingest-gate --repo repo-j --iteration 1 2>&1)
|
|
463
|
+
echo "$INGJ" | grep -q "repo-j/swiftlint: reported=2 gated=1" && { PASS=$((PASS+1)); echo " ok swiftlint via parse_checkstyle: error=HIGH gateado, warning=MEDIUM"; } || { FAIL=$((FAIL+1)); echo " FAIL swiftlint ($INGJ)"; }
|
|
464
|
+
"$PY" -c "
|
|
465
|
+
import json, sys
|
|
466
|
+
node = json.load(open('QA-LEDGER.json'))['repos']['repo-j']
|
|
467
|
+
ids = [i for s in node['iterations'] if s.get('finding_ids') for i in s['finding_ids']]
|
|
468
|
+
ok = any(x.startswith('swiftlint:') and 'Sources/Kit/Kit.swift' in x.replace(chr(92), '/') and not x.replace(chr(92), '/').count(':/') for x in ids)
|
|
469
|
+
sys.exit(0 if ok else 1)" \
|
|
470
|
+
&& { PASS=$((PASS+1)); echo " ok IDs swiftlint repo-relativos (sin colision por basename)"; } \
|
|
471
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL IDs swiftlint no relativizados"; }
|
|
472
|
+
|
|
473
|
+
echo "== T29 acceptance trazable: AC-n cierra por testcase MEDIDO, no por checkbox =="
|
|
474
|
+
# AC-1: checkbox [x] + testcase verde 'test_ac1_*' -> cierra MEDIDO.
|
|
475
|
+
# AC-2: checkbox [x] pero su testcase FALLA -> narrated-only, NO cierra.
|
|
476
|
+
# AC-3: sin marcar y sin test -> abierta. Dimension acceptance = 1/3.
|
|
477
|
+
printf -- "# ACCEPTANCE\n\n- [x] AC-01 alta de cliente valida\n- [x] AC-02 rechazo de duplicado\n- [ ] AC-03 baja logica\n" > ACCEPTANCE.md
|
|
478
|
+
cat > repo-c/reports/junit.xml <<'EOF'
|
|
479
|
+
<?xml version="1.0"?>
|
|
480
|
+
<testsuites><testsuite name="pytest" tests="3" failures="1" errors="0" skipped="0">
|
|
481
|
+
<testcase classname="tests.test_flow" name="test_ac1_alta_ok"/>
|
|
482
|
+
<testcase classname="tests.test_flow" name="test_ac_02_rechazo_duplicado"><failure message="boom"/></testcase>
|
|
483
|
+
<testcase classname="tests.test_misc" name="test_sin_criterio"/>
|
|
484
|
+
</testsuite></testsuites>
|
|
485
|
+
EOF
|
|
486
|
+
RDY=$(run readiness --json 2>/dev/null)
|
|
487
|
+
echo "$RDY" | "$PY" -c "
|
|
488
|
+
import json, sys
|
|
489
|
+
d = json.load(sys.stdin)
|
|
490
|
+
a = d['acceptance']
|
|
491
|
+
ok = (a['traceable'] is True and a['measured_closed'] == ['AC-1']
|
|
492
|
+
and a['narrated_only'] == ['AC-2']
|
|
493
|
+
and abs(d['dimensions']['acceptance']['raw'] - 0.333) < 0.01)
|
|
494
|
+
sys.exit(0 if ok else 1)" \
|
|
495
|
+
&& { PASS=$((PASS+1)); echo " ok AC-1 cierra medido; AC-2 narrated-only (test rojo veta); dim=1/3"; } \
|
|
496
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL acceptance trazable ($(echo "$RDY" | "$PY" -c 'import json,sys;print(json.load(sys.stdin)["acceptance"])' 2>/dev/null))"; }
|
|
497
|
+
run readiness 2>/dev/null | grep -q "narrated-only: AC-2" \
|
|
498
|
+
&& { PASS=$((PASS+1)); echo " ok warning narrated-only visible (measured beats narrated)"; } \
|
|
499
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin warning narrated-only"; }
|
|
500
|
+
# % TERMINADO medido (1.28.0): 1 de 3 AC cerrado por test verde = 33.3%, informativo
|
|
501
|
+
run readiness --json 2>/dev/null | "$PY" -c "import json,sys; sys.exit(0 if json.load(sys.stdin)['acceptance']['measured_pct']==33.3 else 1)" \
|
|
502
|
+
&& { PASS=$((PASS+1)); echo " ok acceptance.measured_pct = 33.3% en --json (1 de 3 medido)"; } \
|
|
503
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL measured_pct mal computado"; }
|
|
504
|
+
run readiness 2>/dev/null | grep -q "acceptance medido: 33.3%" \
|
|
505
|
+
&& { PASS=$((PASS+1)); echo " ok '% terminado' medido visible en la vista default"; } \
|
|
506
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL linea de acceptance medido ausente"; }
|
|
507
|
+
# sin trazabilidad AC-n NO hay % honesto: la linea no debe aparecer
|
|
508
|
+
printf -- "# ACCEPTANCE\n\n- [x] criterio uno\n- [ ] criterio dos\n" > ACCEPTANCE.md
|
|
509
|
+
run readiness 2>/dev/null | grep -q "acceptance medido:" \
|
|
510
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL muestra % medido sin AC-IDs (deshonesto)"; } \
|
|
511
|
+
|| { PASS=$((PASS+1)); echo " ok sin AC-IDs no muestra % medido (honesto)"; }
|
|
512
|
+
printf -- "# ACCEPTANCE\n\n- [x] AC-01 alta de cliente valida\n- [x] AC-02 rechazo de duplicado\n- [ ] AC-03 baja logica\n" > ACCEPTANCE.md
|
|
513
|
+
|
|
514
|
+
echo "== T30 spec-check --acceptance: trazabilidad como FACT =="
|
|
515
|
+
chk "acceptance con AC-IDs -> exit 0" 0 run spec-check --acceptance ACCEPTANCE.md
|
|
516
|
+
printf -- "- [ ] criterio sin id\n" > acc-untraced.md
|
|
517
|
+
chk "cero criterios trazables -> BLOCKED exit 1" 1 run spec-check --acceptance acc-untraced.md
|
|
518
|
+
printf -- "- [ ] AC-01 a\n- [x] AC-1 b\n" > acc-dup.md
|
|
519
|
+
chk "IDs duplicados (AC-01 == AC-1 normalizado) -> exit 1" 1 run spec-check --acceptance acc-dup.md
|
|
520
|
+
|
|
521
|
+
echo "== T31 edges 1.10.0: regex sin falsos positivos + flaky de surefire =="
|
|
522
|
+
# HVAC2/mac1/track12 NO son tags AC; classname jamas taggea (solo el NOMBRE);
|
|
523
|
+
# flaky que paso tras retry (solo <flakyFailure>) = verde; fallo definitivo
|
|
524
|
+
# (<failure> + <rerunFailure>) = rojo y veta.
|
|
525
|
+
"$PY" -c "
|
|
526
|
+
import sys, os, tempfile
|
|
527
|
+
sys.path.insert(0, os.path.dirname(sys.argv[1]))
|
|
528
|
+
import qa_ledger as q
|
|
529
|
+
for name in ('testHVAC2Compressor', 'test_mac1_address', 'testMac1Parse', 'test_track12'):
|
|
530
|
+
assert q._AC_TAG.findall(name) == [], name
|
|
531
|
+
for name, want in (('test_ac1_alta', ['1']), ('testAC01X', ['1']), ('AC-01: alta', ['1'])):
|
|
532
|
+
assert q._AC_TAG.findall(name) == want, name
|
|
533
|
+
xml_ = '''<testsuites><testsuite name=\"s\">
|
|
534
|
+
<testcase classname=\"tests.test_ac9_flow\" name=\"test_sin_tag\"/>
|
|
535
|
+
<testcase classname=\"C\" name=\"test_ac1_flaky\"><flakyFailure message=\"retry\"/></testcase>
|
|
536
|
+
<testcase classname=\"C\" name=\"test_ac2_fallo\"><failure message=\"x\"/><rerunFailure message=\"r\"/></testcase>
|
|
537
|
+
</testsuite></testsuites>'''
|
|
538
|
+
d = tempfile.mkdtemp(); os.makedirs(os.path.join(d, 'reports'))
|
|
539
|
+
open(os.path.join(d, 'reports', 'junit.xml'), 'w').write(xml_)
|
|
540
|
+
t, _stale = q._ac_tags(d, 'python')
|
|
541
|
+
assert 'AC-9' not in t, 'classname no taggea'
|
|
542
|
+
assert t['AC-1'] == {'green': 1, 'red': 0}, 'flaky-que-paso = verde'
|
|
543
|
+
assert t['AC-2'] == {'green': 0, 'red': 1}, 'fallo-tras-reruns = rojo'
|
|
544
|
+
sys.exit(0)" "$QL" \
|
|
545
|
+
&& { PASS=$((PASS+1)); echo " ok sin falsos positivos (HVAC2/mac1/track12); classname no taggea; flaky ok"; } \
|
|
546
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL edges regex/flaky 1.10.0"; }
|
|
547
|
+
|
|
548
|
+
echo "== T32 simplicity: tests FUERA del presupuesto (M9, Topic 51) =="
|
|
549
|
+
# Diff con 6 lineas de prod y 300 de test: el presupuesto solo ve las 6.
|
|
550
|
+
"$PY" -c "
|
|
551
|
+
import io, os, sys
|
|
552
|
+
sys.path.insert(0, os.path.dirname(sys.argv[1]))
|
|
553
|
+
import qa_ledger as q
|
|
554
|
+
prod = ''.join(f'+line {i}\n' for i in range(6))
|
|
555
|
+
test = ''.join(f'+assert {i}\n' for i in range(300))
|
|
556
|
+
diff = (
|
|
557
|
+
'diff --git a/src/main/java/App.java b/src/main/java/App.java\n'
|
|
558
|
+
'--- a/src/main/java/App.java\n+++ b/src/main/java/App.java\n@@ -0,0 +1,6 @@\n' + prod +
|
|
559
|
+
'diff --git a/src/test/java/AppTest.java b/src/test/java/AppTest.java\n'
|
|
560
|
+
'--- /dev/null\n+++ b/src/test/java/AppTest.java\n@@ -0,0 +1,300 @@\n' + test +
|
|
561
|
+
'diff --git a/tests/test_flow.py b/tests/test_flow.py\n'
|
|
562
|
+
'--- /dev/null\n+++ b/tests/test_flow.py\n@@ -0,0 +1,2 @@\n+x = 1\n+y = 2\n'
|
|
563
|
+
)
|
|
564
|
+
m = q._simplicity_metrics(diff, 4)
|
|
565
|
+
assert m['lines_added'] == 6, m
|
|
566
|
+
assert m['files_changed'] == 1, m
|
|
567
|
+
assert m['test_lines_added'] == 302, m
|
|
568
|
+
assert m['test_files_changed'] == 2, m
|
|
569
|
+
# clasificador: convenciones de los 9 stacks, sin tragar backtest.cpp
|
|
570
|
+
tf = q._is_simplicity_test_file
|
|
571
|
+
assert tf('src/test/java/AppTest.java') and tf('tests/test_flow.py')
|
|
572
|
+
assert tf('src/integrationTest/kotlin/FlowTest.kt') and tf('lib/foo.spec.ts')
|
|
573
|
+
assert tf('Api.Tests/FooTests.cs') and tf('pkg/foo_test.go')
|
|
574
|
+
assert not tf('src/main/java/App.java') and not tf('src/backtest.cpp')
|
|
575
|
+
assert not tf('src/protest.cc') and not tf('Sources/Core/Engine.swift')
|
|
576
|
+
sys.exit(0)" "$QL" \
|
|
577
|
+
&& { PASS=$((PASS+1)); echo " ok presupuesto solo prod (6 lineas, 1 archivo); tests contados aparte (+302 en 2)"; } \
|
|
578
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL tests dentro del presupuesto de simplicity"; }
|
|
579
|
+
|
|
580
|
+
echo "== T33 gate-check: borrado de tests go/dotnet/js tambien bloquea (clasificador unificado) =="
|
|
581
|
+
# gate-check reusa el clasificador de los 9 stacks + TESTDEF ampliado: la promesa
|
|
582
|
+
# 'borrar tests lo bloquea gate-check' vale para TODAS las convenciones, no solo JVM.
|
|
583
|
+
printf -- "diff --git a/pkg/foo_test.go b/pkg/foo_test.go\n--- a/pkg/foo_test.go\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-func TestFoo(t *testing.T) {\n-\tassertEqual(t, 1, 1)\n" > del-go.diff
|
|
584
|
+
chk "delete de foo_test.go (Go) -> BLOCKER" 1 run gate-check --diff del-go.diff
|
|
585
|
+
printf -- "diff --git a/Api.Tests/CalcTests.cs b/Api.Tests/CalcTests.cs\n--- a/Api.Tests/CalcTests.cs\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-[Fact]\n-public void Suma_Valida() { Assert.Equal(2, Calc.Suma(1,1)); }\n" > del-cs.diff
|
|
586
|
+
chk "delete de Api.Tests/*.cs (xunit [Fact]) -> BLOCKER" 1 run gate-check --diff del-cs.diff
|
|
587
|
+
printf -- "diff --git a/__tests__/flow.test.ts b/__tests__/flow.test.ts\n--- a/__tests__/flow.test.ts\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-it('valida el flujo', () => {\n- expect(flow()).toBe(true);\n" > del-ts.diff
|
|
588
|
+
chk "delete de __tests__/*.test.ts (it/expect) -> BLOCKER" 1 run gate-check --diff del-ts.diff
|
|
589
|
+
|
|
590
|
+
echo "== T34 gate-check: secret-scan (Topic 43) — secretos agregados bloquean como hecho =="
|
|
591
|
+
printf -- "diff --git a/src/cfg.py b/src/cfg.py\n--- a/src/cfg.py\n+++ b/src/cfg.py\n@@ -0,0 +1,1 @@\n+AWS_KEY = \"AKIAIOSFODNN7EXAMPLE\"\n" > sec-akia.diff
|
|
592
|
+
chk "AWS access key agregada -> BLOCKER" 1 run gate-check --diff sec-akia.diff
|
|
593
|
+
printf -- "diff --git a/deploy/id_rsa b/deploy/id_rsa\n--- /dev/null\n+++ b/deploy/id_rsa\n@@ -0,0 +1,1 @@\n+-----BEGIN PRIVATE KEY-----\n" > sec-pem.diff
|
|
594
|
+
chk "clave privada PEM agregada -> BLOCKER" 1 run gate-check --diff sec-pem.diff
|
|
595
|
+
printf -- "diff --git a/certs/client.p12 b/certs/client.p12\nindex 0000000..1111111 100644\nBinary files a/certs/client.p12 and b/certs/client.p12 differ\n" > sec-p12.diff
|
|
596
|
+
chk "contenedor .p12 binario agregado -> BLOCKER" 1 run gate-check --diff sec-p12.diff
|
|
597
|
+
printf -- "diff --git a/certs/old.p12 b/certs/old.p12\ndeleted file mode 100644\nindex 1111111..0000000\nBinary files a/certs/old.p12 and /dev/null differ\n" > sec-del.diff
|
|
598
|
+
chk "BORRAR un .p12 no bloquea (sacar secretos es bueno)" 0 run gate-check --diff sec-del.diff
|
|
599
|
+
printf -- "diff --git a/src/cfg.py b/src/cfg.py\n--- a/src/cfg.py\n+++ b/src/cfg.py\n@@ -0,0 +1,1 @@\n+password = \"hunter2secreto\"\n" > sec-lit.diff
|
|
600
|
+
chk "literal password generico -> REVIEW exit 0 (advisory)" 0 run gate-check --diff sec-lit.diff
|
|
601
|
+
chk "literal password generico + --strict -> exit 1" 1 run gate-check --diff sec-lit.diff --strict
|
|
602
|
+
|
|
603
|
+
echo "== T35 ledger atomico: checksum de integridad + carga blindada =="
|
|
604
|
+
# el ledger recien escrito trae integrity y carga verificado
|
|
605
|
+
chk "ledger con integrity carga OK" 0 run summary
|
|
606
|
+
# mutacion EXTERNA (JSON valido, contenido cambiado, hash viejo) -> bloquea
|
|
607
|
+
"$PY" -c "
|
|
608
|
+
import json, sys
|
|
609
|
+
d = json.load(open('QA-LEDGER.json', encoding='utf-8'))
|
|
610
|
+
assert 'integrity' in d and d['integrity']['sha256'], 'falta integrity en ledger nuevo'
|
|
611
|
+
d['config']['defaults']['coverage_threshold'] = 1
|
|
612
|
+
json.dump(d, open('QA-LEDGER.json', 'w', encoding='utf-8'))"
|
|
613
|
+
chk "mutacion externa (checksum roto) -> bloquea exit 1" 1 run summary
|
|
614
|
+
run summary 2>&1 | grep -qi "checksum" \
|
|
615
|
+
&& { PASS=$((PASS+1)); echo " ok mensaje de checksum presente (no traceback)"; } \
|
|
616
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin mensaje de checksum"; }
|
|
617
|
+
# aceptacion humana explicita: borrar 'integrity' -> legacy, carga sin verificar
|
|
618
|
+
"$PY" -c "
|
|
619
|
+
import json
|
|
620
|
+
d = json.load(open('QA-LEDGER.json', encoding='utf-8'))
|
|
621
|
+
del d['integrity']
|
|
622
|
+
json.dump(d, open('QA-LEDGER.json', 'w', encoding='utf-8'))"
|
|
623
|
+
chk "legacy sin integrity -> carga OK (adopcion incremental)" 0 run summary
|
|
624
|
+
# JSON corrupto (escritura parcial) -> mensaje de recuperacion, no traceback
|
|
625
|
+
"$PY" -c "open('QA-LEDGER.json','a',encoding='utf-8').write('{trunc')"
|
|
626
|
+
chk "JSON corrupto -> exit 1 con mensaje" 1 run summary
|
|
627
|
+
run summary 2>&1 | grep -q "Traceback" \
|
|
628
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL traceback crudo en ledger corrupto"; } \
|
|
629
|
+
|| { PASS=$((PASS+1)); echo " ok sin traceback: mensaje de recuperacion"; }
|
|
630
|
+
# restaurar el ledger para lo que venga despues (re-init limpio)
|
|
631
|
+
run init --config uscha.config.json >/dev/null 2>&1
|
|
632
|
+
|
|
633
|
+
echo "== T36 plateau/stop-signal: advisory sobre el historico (Know When to Stop) =="
|
|
634
|
+
# (a) stall: findings gateados SUBIENDO 3 ciclos COMPLETOS en repo-a -> re-planear.
|
|
635
|
+
# Con qa_tools_order configurado solo cuentan ciclos con TODAS las tools logueadas.
|
|
636
|
+
for i in 1 2 3; do
|
|
637
|
+
run log-step --repo repo-a --tool code-review --iteration $i \
|
|
638
|
+
--reported $((i+3)) --gated-reported $((i+3)) --tests-passed true >/dev/null 2>&1
|
|
639
|
+
for t in judgment-day improve; do
|
|
640
|
+
run log-step --repo repo-a --tool $t --iteration $i \
|
|
641
|
+
--gated-reported 0 --tests-passed true >/dev/null 2>&1
|
|
642
|
+
done
|
|
643
|
+
done
|
|
644
|
+
run readiness 2>/dev/null | grep -q "stall: repo-a" \
|
|
645
|
+
&& { PASS=$((PASS+1)); echo " ok stall detectado (findings 4->5->6, iterar no acerca)"; } \
|
|
646
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin aviso de stall"; }
|
|
647
|
+
# (b) mismo patron pero con el ULTIMO ciclo INCOMPLETO (1 de 3 tools) -> no cuenta,
|
|
648
|
+
# la serie completa queda corta y el stall NO dispara (sin contaminacion parcial)
|
|
649
|
+
run log-step --repo repo-a --tool code-review --iteration 4 \
|
|
650
|
+
--gated-reported 9 --tests-passed true >/dev/null 2>&1
|
|
651
|
+
run readiness 2>/dev/null | grep -q "stall: repo-a" \
|
|
652
|
+
&& { PASS=$((PASS+1)); echo " ok ciclo 4 parcial no rompe la serie (stall sigue por ciclos 1-3)"; } \
|
|
653
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL ciclo parcial altero la deteccion"; }
|
|
654
|
+
# (c) serie BAJANDO no es stall (hay progreso)
|
|
655
|
+
run init --config uscha.config.json >/dev/null 2>&1
|
|
656
|
+
for i in 1 2 3; do
|
|
657
|
+
run log-step --repo repo-a --tool code-review --iteration $i \
|
|
658
|
+
--reported $((7-i*2)) --gated-reported $((7-i*2)) --tests-passed true >/dev/null 2>&1
|
|
659
|
+
for t in judgment-day improve; do
|
|
660
|
+
run log-step --repo repo-a --tool $t --iteration $i \
|
|
661
|
+
--gated-reported 0 --tests-passed true >/dev/null 2>&1
|
|
662
|
+
done
|
|
663
|
+
done
|
|
664
|
+
run readiness 2>/dev/null | grep -q "stall: repo-a" \
|
|
665
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL stall con serie bajando (5->3->1 es progreso)"; } \
|
|
666
|
+
|| { PASS=$((PASS+1)); echo " ok serie bajando (5->3->1) no dispara stall"; }
|
|
667
|
+
# (c) stop-signal: repo unico convergido, cero facts bloqueantes -> candidato a PR
|
|
668
|
+
printf '{ "defaults": { "acceptance_file": "ACCEPTANCE.md", "qa_tools_order": ["code-review","judgment-day","improve"] },\n "repos": [ {"name":"solo","path":"repo-c","type":"python"} ], "integration": {"enabled": false} }\n' > c-solo.json
|
|
669
|
+
run init --config c-solo.json --out L-solo.json >/dev/null 2>&1
|
|
670
|
+
for t in code-review judgment-day improve; do
|
|
671
|
+
run log-step --ledger L-solo.json --repo solo --tool $t --iteration 1 \
|
|
672
|
+
--gated-reported 0 --files-changed 0 --tests-passed true >/dev/null 2>&1
|
|
673
|
+
done
|
|
674
|
+
RDY=$(run readiness --ledger L-solo.json --json 2>/dev/null)
|
|
675
|
+
echo "$RDY" | "$PY" -c "
|
|
676
|
+
import json, sys
|
|
677
|
+
d = json.load(sys.stdin)
|
|
678
|
+
a = d['advice']
|
|
679
|
+
sys.exit(0 if a['stop_signal'] is True and a['stalled_repos'] == [] else 1)" \
|
|
680
|
+
&& { PASS=$((PASS+1)); echo " ok stop-signal: convergido + cero facts bloqueantes -> candidato a PR"; } \
|
|
681
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL stop-signal no emitido ($(echo "$RDY" | "$PY" -c 'import json,sys;print(json.load(sys.stdin).get("advice"))' 2>/dev/null))"; }
|
|
682
|
+
|
|
683
|
+
echo "== T37 golden scrub: volatiles declarados enmascaran, el masking es VISIBLE =="
|
|
684
|
+
# Nota INV-GOLDEN-01: crear un .approved es un acto HUMANO incluso en tests —
|
|
685
|
+
# igual que el path CLEAN byte-a-byte, el path CLEAN-via-scrub NO se auto-testea.
|
|
686
|
+
# La mecanica de scrub se prueba a nivel FUNCION (sin fixtures aprobados).
|
|
687
|
+
"$PY" -c "
|
|
688
|
+
import sys, os, re
|
|
689
|
+
sys.path.insert(0, os.path.dirname(sys.argv[1]))
|
|
690
|
+
import qa_ledger as q
|
|
691
|
+
rules = [(re.compile(r'\d{4}-\d{2}-\d{2}T[0-9:Z.+-]+'), '<TS>', 'ts')]
|
|
692
|
+
counts = {}
|
|
693
|
+
a = q._scrub(b'ok at 2026-07-03T10:00:00Z\nvalor=42\n', rules, counts)
|
|
694
|
+
b = q._scrub(b'ok at 2026-07-01T09:30:00Z\nvalor=42\n', rules, counts)
|
|
695
|
+
assert a == b == b'ok at <TS>\nvalor=42\n', (a, b)
|
|
696
|
+
assert counts['ts'] == 2, counts # el masking se CUENTA, no es magia
|
|
697
|
+
# divergencia real (mas alla del volatil) NO se enmascara
|
|
698
|
+
c = q._scrub(b'ok at 2026-07-03T10:00:00Z\nvalor=99\n', rules, counts)
|
|
699
|
+
assert c != a
|
|
700
|
+
# binario: intacto, sigue byte a byte
|
|
701
|
+
raw = bytes([0xff, 0xfe, 0x00, 0x42])
|
|
702
|
+
assert q._scrub(raw, rules, counts) == raw
|
|
703
|
+
sys.exit(0)" "$QL" \
|
|
704
|
+
&& { PASS=$((PASS+1)); echo " ok scrub enmascara volatiles, cuenta sustituciones, binario intacto"; } \
|
|
705
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL mecanica de scrub"; }
|
|
706
|
+
mkdir -p gsc
|
|
707
|
+
printf "ok at 2026-07-03T10:00:00Z\n" > gsc/out.received.txt
|
|
708
|
+
printf '{ "rules": [ {"pattern": "\\\\d{4}-\\\\d{2}-\\\\d{2}T[0-9:Z.+-]+", "replace": "<TS>"} ] }\n' > gsc/golden.scrub.json
|
|
709
|
+
# el scrub NO fabrica aprobacion: .received sin .approved sigue DIVERGE
|
|
710
|
+
chk "scrub activo sin .approved -> sigue DIVERGE exit 1" 1 run golden-diff --dir gsc
|
|
711
|
+
# scrub invalido = error de config explicito, jamas se saltea en silencio
|
|
712
|
+
printf '{ "rules": [ {"pattern": "([", "replace": "x"} ] }\n' > gsc/golden.scrub.json
|
|
713
|
+
chk "scrub invalido (regex rota) -> exit 2 (config error)" 2 run golden-diff --dir gsc
|
|
714
|
+
printf '[ {"pattern": "x", "replace": "y"} ]\n' > gsc/golden.scrub.json
|
|
715
|
+
chk "scrub con shape invalida (lista a secas) -> exit 2, no traceback" 2 run golden-diff --dir gsc
|
|
716
|
+
printf '{}\n' > gsc/golden.scrub.json
|
|
717
|
+
chk "scrub sin key rules (typo) -> exit 2, no degrada a cero reglas" 2 run golden-diff --dir gsc
|
|
718
|
+
# gate-check: editar el scrub es señal blanda visible
|
|
719
|
+
printf -- "diff --git a/fixtures/golden.scrub.json b/fixtures/golden.scrub.json\n--- a/fixtures/golden.scrub.json\n+++ b/fixtures/golden.scrub.json\n@@ -0,0 +1,1 @@\n+{ \"rules\": [ {\"pattern\": \".*\", \"replace\": \"\"} ] }\n" > scrub-edit.diff
|
|
720
|
+
run gate-check --diff scrub-edit.diff 2>/dev/null | grep -q "scrub" \
|
|
721
|
+
&& { PASS=$((PASS+1)); echo " ok gate-check flaggea edicion de reglas de scrub (REVIEW)"; } \
|
|
722
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL edicion de scrub invisible para gate-check"; }
|
|
723
|
+
chk "edicion de scrub + --strict -> exit 1" 1 run gate-check --diff scrub-edit.diff --strict
|
|
724
|
+
printf -- "diff --git a/fixtures/golden.scrub.json b/fixtures/golden.scrub.json\n--- a/fixtures/golden.scrub.json\n+++ /dev/null\n@@ -1,1 +0,0 @@\n-{ \"rules\": [ {\"pattern\": \"x\", \"replace\": \"y\"} ] }\n" > scrub-del.diff
|
|
725
|
+
run gate-check --diff scrub-del.diff 2>/dev/null | grep -q "scrub" \
|
|
726
|
+
&& { PASS=$((PASS+1)); echo " ok BORRAR el scrub tambien se flaggea (borrar reglas es editarlas)"; } \
|
|
727
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL borrado de scrub invisible"; }
|
|
728
|
+
|
|
729
|
+
echo "== T38 regression-check: cierre sin test = NARRADO, jamas medido (Find Bugs Once) =="
|
|
730
|
+
# fix SIN tocar tests: solo produccion cambiada
|
|
731
|
+
printf -- "diff --git a/src/main/java/App.java b/src/main/java/App.java\n--- a/src/main/java/App.java\n+++ b/src/main/java/App.java\n@@ -0,0 +1,1 @@\n+if (x != null) { return x.trim(); }\n" > fix-sin-test.diff
|
|
732
|
+
chk "cierre sin test -> NARRATED, advisory exit 0" 0 run regression-check --repo repo-a --fixed 2 --diff fix-sin-test.diff
|
|
733
|
+
run regression-check --repo repo-a --fixed 2 --diff fix-sin-test.diff 2>/dev/null | grep -q "NARRATED" \
|
|
734
|
+
&& { PASS=$((PASS+1)); echo " ok verdict NARRATED visible (cierre narrado, no medido)"; } \
|
|
735
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin verdict NARRATED"; }
|
|
736
|
+
chk "cierre sin test + --strict -> exit 1" 1 run regression-check --repo repo-a --fixed 2 --diff fix-sin-test.diff --strict
|
|
737
|
+
# fix CON test que reproduce: MEASURED
|
|
738
|
+
printf -- "diff --git a/src/main/java/App.java b/src/main/java/App.java\n--- a/src/main/java/App.java\n+++ b/src/main/java/App.java\n@@ -0,0 +1,1 @@\n+if (x != null) { return x.trim(); }\ndiff --git a/src/test/java/AppTest.java b/src/test/java/AppTest.java\n--- a/src/test/java/AppTest.java\n+++ b/src/test/java/AppTest.java\n@@ -0,0 +1,2 @@\n+@Test\n+void testNullInputRegression() { assertNull(app.run(null)); }\n" > fix-con-test.diff
|
|
739
|
+
chk "cierre con test nuevo -> MEASURED exit 0 (aun con --strict)" 0 run regression-check --repo repo-a --fixed 2 --diff fix-con-test.diff --strict
|
|
740
|
+
# nada cerrado -> N/A, nada que exigir
|
|
741
|
+
chk "fixed 0 -> N/A exit 0 (aun con --strict)" 0 run regression-check --repo repo-a --fixed 0 --diff fix-sin-test.diff --strict
|
|
742
|
+
# gaming barato: UNA linea EN BLANCO en un test file NO es evidencia
|
|
743
|
+
printf -- "diff --git a/src/test/java/AppTest.java b/src/test/java/AppTest.java\n--- a/src/test/java/AppTest.java\n+++ b/src/test/java/AppTest.java\n@@ -0,0 +1,1 @@\n+\n" > fix-blank.diff
|
|
744
|
+
chk "linea en blanco en test file NO es evidencia -> NARRATED --strict exit 1" 1 \
|
|
745
|
+
run regression-check --repo repo-a --fixed 2 --diff fix-blank.diff --strict
|
|
746
|
+
# evidencia debil (linea de test sin testdef ni assert) -> MEASURED pero avisa
|
|
747
|
+
printf -- "diff --git a/src/test/java/AppTest.java b/src/test/java/AppTest.java\n--- a/src/test/java/AppTest.java\n+++ b/src/test/java/AppTest.java\n@@ -0,0 +1,1 @@\n+// nota\n" > fix-weak.diff
|
|
748
|
+
run regression-check --repo repo-a --fixed 2 --diff fix-weak.diff 2>/dev/null | grep -q "DEBIL" \
|
|
749
|
+
&& { PASS=$((PASS+1)); echo " ok evidencia debil (sin testdef/assert) marcada para el ojo humano"; } \
|
|
750
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL evidencia debil invisible"; }
|
|
751
|
+
|
|
752
|
+
echo "== T39 procedencia de umbrales: requerimiento (config) vs default del kit =="
|
|
753
|
+
# cap DECLARADO en config (tests_red: 1 — siempre muerde con el junit rojo de repo-c)
|
|
754
|
+
printf '{ "defaults": { "acceptance_file": "ACCEPTANCE.md", "readiness_caps": {"tests_red": 1} },\n "repos": [ {"name":"solo","path":"repo-c","type":"python"} ], "integration": {"enabled": false} }\n' > c-caps.json
|
|
755
|
+
run init --config c-caps.json --out L-caps.json >/dev/null 2>&1
|
|
756
|
+
run snapshot --ledger L-caps.json --repo solo >/dev/null 2>&1
|
|
757
|
+
RDY=$(run readiness --ledger L-caps.json --json 2>/dev/null)
|
|
758
|
+
echo "$RDY" | "$PY" -c "
|
|
759
|
+
import json, sys
|
|
760
|
+
d = json.load(sys.stdin)
|
|
761
|
+
td = d['thresholds_declared']
|
|
762
|
+
ok = (d['cap_source'] == 'requerimiento (config)'
|
|
763
|
+
and td['readiness_caps'] == ['tests_red']
|
|
764
|
+
and td['coverage_threshold'] is False)
|
|
765
|
+
sys.exit(0 if ok else 1)" \
|
|
766
|
+
&& { PASS=$((PASS+1)); echo " ok cap declarado en config etiquetado 'requerimiento (config)'"; } \
|
|
767
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL procedencia en readiness ($(echo "$RDY" | "$PY" -c 'import json,sys;d=json.load(sys.stdin);print(d.get("cap_source"),d.get("thresholds_declared"))' 2>/dev/null))"; }
|
|
768
|
+
run readiness --ledger L-caps.json 2>/dev/null | grep -q "requerimiento (config)" \
|
|
769
|
+
&& { PASS=$((PASS+1)); echo " ok etiqueta de procedencia visible en el texto del cap"; } \
|
|
770
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL etiqueta de procedencia ausente en texto"; }
|
|
771
|
+
# el sandbox principal no declara caps: la lista de declarados queda vacia
|
|
772
|
+
run readiness --json 2>/dev/null | "$PY" -c "
|
|
773
|
+
import json, sys
|
|
774
|
+
d = json.load(sys.stdin)
|
|
775
|
+
sys.exit(0 if d['thresholds_declared']['readiness_caps'] == [] else 1)" \
|
|
776
|
+
&& { PASS=$((PASS+1)); echo " ok sin caps declarados -> lista vacia (defaults = opinion del kit)"; } \
|
|
777
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL thresholds_declared del sandbox principal"; }
|
|
778
|
+
# simplicity: sin config -> todos default (aviso); con presupuesto CLI -> declarado
|
|
779
|
+
printf -- "diff --git a/src/A.java b/src/A.java\n--- a/src/A.java\n+++ b/src/A.java\n@@ -0,0 +1,1 @@\n+int x = 1;\n" > simp-tiny.diff
|
|
780
|
+
run simplicity-check --diff simp-tiny.diff 2>/dev/null | grep -q "defaults del kit" \
|
|
781
|
+
&& { PASS=$((PASS+1)); echo " ok simplicity avisa: presupuestos = opinion del kit"; } \
|
|
782
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin aviso de presupuestos default"; }
|
|
783
|
+
run simplicity-check --diff simp-tiny.diff --max-lines-added 100 --json 2>/dev/null | "$PY" -c "
|
|
784
|
+
import json, sys
|
|
785
|
+
d = json.load(sys.stdin)
|
|
786
|
+
sys.exit(0 if d['budgets_declared'] == ['max_lines_added'] else 1)" \
|
|
787
|
+
&& { PASS=$((PASS+1)); echo " ok presupuesto declarado por CLI listado en budgets_declared"; } \
|
|
788
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL budgets_declared no refleja el CLI"; }
|
|
789
|
+
|
|
790
|
+
echo "== T40 phase: FSM DERIVADA del ledger — el estado se computa, no se declara =="
|
|
791
|
+
mkdir -p repo-x
|
|
792
|
+
printf '{ "defaults": { "acceptance_file": "ACCEPTANCE.md", "qa_tools_order": ["code-review","judgment-day","improve"] },\n "repos": [ {"name":"fsm","path":"repo-x","type":"go"} ], "integration": {"enabled": false} }\n' > c-fsm.json
|
|
793
|
+
run init --config c-fsm.json --out L-fsm.json >/dev/null 2>&1
|
|
794
|
+
chk "ledger virgen -> plan" 0 run phase --ledger L-fsm.json --repo fsm --require plan
|
|
795
|
+
run snapshot --ledger L-fsm.json --repo fsm >/dev/null 2>&1
|
|
796
|
+
chk "snapshot medido sin QA -> build" 0 run phase --ledger L-fsm.json --repo fsm --require build
|
|
797
|
+
run log-step --ledger L-fsm.json --repo fsm --tool code-review --iteration 1 \
|
|
798
|
+
--gated-reported 2 --tests-passed true >/dev/null 2>&1
|
|
799
|
+
chk "pasos de QA sin converger -> qa" 0 run phase --ledger L-fsm.json --repo fsm --require qa
|
|
800
|
+
chk "pedir pr-ready con findings abiertos -> exit 1 (los hechos mandan)" 1 \
|
|
801
|
+
run phase --ledger L-fsm.json --repo fsm --require pr-ready
|
|
802
|
+
run escalate --ledger L-fsm.json --repo fsm --reason "duda de diseño" >/dev/null 2>&1
|
|
803
|
+
chk "escalacion abierta -> escalated (pisa todo)" 0 run phase --ledger L-fsm.json --repo fsm --require escalated
|
|
804
|
+
run resolve-escalation --ledger L-fsm.json --repo fsm --note ok >/dev/null 2>&1
|
|
805
|
+
for t in code-review judgment-day improve; do
|
|
806
|
+
run log-step --ledger L-fsm.json --repo fsm --tool $t --iteration 2 \
|
|
807
|
+
--gated-reported 0 --files-changed 0 --tests-passed true >/dev/null 2>&1
|
|
808
|
+
done
|
|
809
|
+
chk "convergido + limpio -> pr-ready" 0 run phase --ledger L-fsm.json --repo fsm --require pr-ready
|
|
810
|
+
|
|
811
|
+
echo "== T41 spike/*: codigo descartable por contrato — jamas pasa el gate de PR =="
|
|
812
|
+
# repo-x convergido (pr-ready por hechos, de T40) pero en rama spike/* -> DENEGADO
|
|
813
|
+
git init -q repo-x 2>/dev/null
|
|
814
|
+
git -C repo-x symbolic-ref HEAD refs/heads/spike/idea-loca
|
|
815
|
+
chk "pr-ready por hechos PERO rama spike/* -> exit 1" 1 \
|
|
816
|
+
run phase --ledger L-fsm.json --repo fsm --require pr-ready
|
|
817
|
+
run phase --ledger L-fsm.json --repo fsm --require pr-ready 2>/dev/null | grep -q "ADR con las lecciones" \
|
|
818
|
+
&& { PASS=$((PASS+1)); echo " ok mensaje del contrato de spike visible (ADR, no PR)"; } \
|
|
819
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin mensaje de contrato de spike"; }
|
|
820
|
+
# misma rama consultada SIN --require: informa, no bloquea
|
|
821
|
+
chk "phase sin --require en rama spike -> exit 0 (informa, no gatea)" 0 \
|
|
822
|
+
run phase --ledger L-fsm.json --repo fsm
|
|
823
|
+
git -C repo-x symbolic-ref HEAD refs/heads/main
|
|
824
|
+
chk "rama normal -> pr-ready OK de nuevo" 0 \
|
|
825
|
+
run phase --ledger L-fsm.json --repo fsm --require pr-ready
|
|
826
|
+
# default sin --fixed: lee la suma de 'fixed' de la ultima iteracion del ledger
|
|
827
|
+
run init --config uscha.config.json >/dev/null 2>&1
|
|
828
|
+
run log-step --repo repo-a --tool code-review --iteration 1 --fixed 3 --tests-passed true >/dev/null 2>&1
|
|
829
|
+
run regression-check --repo repo-a --diff fix-sin-test.diff --json 2>/dev/null | "$PY" -c "
|
|
830
|
+
import json, sys
|
|
831
|
+
d = json.load(sys.stdin)
|
|
832
|
+
sys.exit(0 if d['fixed'] == 3 and d['verdict'] == 'NARRATED' else 1)" \
|
|
833
|
+
&& { PASS=$((PASS+1)); echo " ok sin --fixed lee la ultima iteracion del ledger (fixed=3)"; } \
|
|
834
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL lookup de fixed en el ledger"; }
|
|
835
|
+
|
|
836
|
+
echo "== T42 doctor: diagnostico de la instalacion (flutter-doctor spirit) =="
|
|
837
|
+
chk "doctor en sandbox con config -> exit 0 (avisos no fallan)" 0 run doctor
|
|
838
|
+
run doctor --json 2>/dev/null | "$PY" -c "
|
|
839
|
+
import json, sys
|
|
840
|
+
d = json.load(sys.stdin)
|
|
841
|
+
sk = next(c for c in d['checks'] if c['title'].startswith('skills'))
|
|
842
|
+
ok = (d['errors'] == 0 and d['global_install'] is False
|
|
843
|
+
and sk['level'] == 'ok'
|
|
844
|
+
and any(c['title'].startswith('proyecto:') for c in d['checks'])
|
|
845
|
+
and any(c['title'].startswith('ACCEPTANCE') and c['level'] == 'ok'
|
|
846
|
+
for c in d['checks']))
|
|
847
|
+
sys.exit(0 if ok else 1)" \
|
|
848
|
+
&& { PASS=$((PASS+1)); echo " ok doctor: 6/6 skills, install por proyecto, config y ACCEPTANCE leidos"; } \
|
|
849
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL doctor json"; }
|
|
850
|
+
# ledger corrupto = ERROR (no aviso): el doctor debe salir 1
|
|
851
|
+
"$PY" -c "open('QA-LEDGER.json','a',encoding='utf-8').write('{trunc')"
|
|
852
|
+
chk "doctor con ledger corrupto -> exit 1" 1 run doctor
|
|
853
|
+
run init --config uscha.config.json >/dev/null 2>&1
|
|
854
|
+
|
|
855
|
+
echo "== T43 rubric layer: agnostico — el grader.json se llena A MANO, sin LLM =="
|
|
856
|
+
cat > RUBRIC.md <<'EOF'
|
|
857
|
+
# RUBRIC — smoke
|
|
858
|
+
threshold: 0.80
|
|
859
|
+
## Criterios
|
|
860
|
+
- [ ] RB-01 (peso 3) — errores sanos
|
|
861
|
+
- [ ] RB-02 (peso 1) — convenciones del repo
|
|
862
|
+
## Criterios negativos
|
|
863
|
+
- [ ] RB-NEG-01 (peso 2) — comentarios que narran el cambio
|
|
864
|
+
EOF
|
|
865
|
+
chk "spec-check --rubric valida -> exit 0" 0 run spec-check --rubric RUBRIC.md
|
|
866
|
+
printf -- "# RUBRIC\n- [ ] RB-01 a\n- [ ] RB-1 b\n" > rub-dup.md
|
|
867
|
+
chk "IDs duplicados (RB-01 == RB-1) -> exit 1" 1 run spec-check --rubric rub-dup.md
|
|
868
|
+
printf -- "# RUBRIC\n- [ ] RB-01 a\n" > rub-nothr.md
|
|
869
|
+
chk "sin threshold -> exit 1" 1 run spec-check --rubric rub-nothr.md
|
|
870
|
+
# grade a mano: RB-01 pass con evidencia (3), RB-02 pass SIN evidencia (no puntua),
|
|
871
|
+
# negativo no aparece -> score 3/4 = 0.75 < 0.80 -> BELOW
|
|
872
|
+
cat > grader.json <<'EOF'
|
|
873
|
+
{ "criteria": [
|
|
874
|
+
{"id": "RB-01", "verdict": "pass", "evidence": "src/x.py:42 — timeout+retry", "note": "ok"},
|
|
875
|
+
{"id": "RB-02", "verdict": "pass", "evidence": "", "note": "sin cita"},
|
|
876
|
+
{"id": "RB-NEG-01", "verdict": "pass", "evidence": "", "note": "no aparece"} ] }
|
|
877
|
+
EOF
|
|
878
|
+
chk "BELOW threshold sin gate declarado -> advisory exit 0" 0 \
|
|
879
|
+
run rubric-ingest --repo repo-a --report grader.json --rubric RUBRIC.md
|
|
880
|
+
run rubric-ingest --repo repo-a --report grader.json --rubric RUBRIC.md --json 2>/dev/null | "$PY" -c "
|
|
881
|
+
import json, sys
|
|
882
|
+
d = json.load(sys.stdin)
|
|
883
|
+
ok = (d['verdict'] == 'BELOW' and abs(d['score'] - 0.75) < 0.001
|
|
884
|
+
and d['unsupported'] == ['RB-2'] and d['gated'] is False)
|
|
885
|
+
sys.exit(0 if ok else 1)" \
|
|
886
|
+
&& { PASS=$((PASS+1)); echo " ok evidence-or-nothing: pass sin cita no puntua (0.75 < 0.80)"; } \
|
|
887
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL contrato del grader"; }
|
|
888
|
+
run readiness 2>/dev/null | grep -q "rubrica repo-a" \
|
|
889
|
+
&& { PASS=$((PASS+1)); echo " ok readiness muestra el grade como advisory (no dimension)"; } \
|
|
890
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL rubrica invisible en readiness"; }
|
|
891
|
+
# gate DECLARADO: below-threshold bloquea convergencia via ledger.
|
|
892
|
+
# Primero repo-a CONVERGE (ciclo limpio) — sin esto el check seria VACUO
|
|
893
|
+
# (converged ya sale 1 en un ledger virgen por 'no agent steps').
|
|
894
|
+
for t in code-review judgment-day improve; do
|
|
895
|
+
run log-step --repo repo-a --tool $t --iteration 9 \
|
|
896
|
+
--gated-reported 0 --files-changed 0 --tests-passed true >/dev/null 2>&1
|
|
897
|
+
done
|
|
898
|
+
chk "repo-a converge ANTES del gate (sanidad del fixture)" 0 run converged --repo repo-a
|
|
899
|
+
chk "BELOW con --gate -> exit 1" 1 \
|
|
900
|
+
run rubric-ingest --repo repo-a --report grader.json --rubric RUBRIC.md --gate
|
|
901
|
+
chk "convergencia bloqueada por rubric:grade gateado" 1 run converged --repo repo-a
|
|
902
|
+
# threshold malformado ('0.8.0') = ausente, no traceback
|
|
903
|
+
printf -- "# RUBRIC\nthreshold: 0.8.0\n- [ ] RB-01 x\n" > rub-badthr.md
|
|
904
|
+
chk "threshold malformado -> exit 1 sin traceback" 1 run spec-check --rubric rub-badthr.md
|
|
905
|
+
# dos veredictos para el mismo criterio (RB-01 y RB-1) = contrato roto
|
|
906
|
+
printf -- '{ "criteria": [ {"id": "RB-01", "verdict": "pass", "evidence": "x:1", "note": ""}, {"id": "RB-1", "verdict": "fail", "evidence": "", "note": ""} ] }\n' > grader-dup.json
|
|
907
|
+
chk "IDs duplicados en el reporte -> exit 1 (un veredicto por criterio)" 1 \
|
|
908
|
+
run rubric-ingest --repo repo-a --report grader-dup.json --rubric RUBRIC.md
|
|
909
|
+
# grade limpio (todo con evidencia, negativo sin matchear) -> PASS y limpia el gate
|
|
910
|
+
cat > grader-ok.json <<'EOF'
|
|
911
|
+
{ "criteria": [
|
|
912
|
+
{"id": "RB-01", "verdict": "pass", "evidence": "src/x.py:42 — ok", "note": "ok"},
|
|
913
|
+
{"id": "RB-02", "verdict": "pass", "evidence": "src/y.py:7 — snake_case", "note": "ok"},
|
|
914
|
+
{"id": "RB-NEG-01", "verdict": "pass", "evidence": "", "note": "no aparece"} ] }
|
|
915
|
+
EOF
|
|
916
|
+
chk "grade limpio con --gate -> exit 0 (PASS)" 0 \
|
|
917
|
+
run rubric-ingest --repo repo-a --report grader-ok.json --rubric RUBRIC.md --gate
|
|
918
|
+
chk "gate limpio libera la convergencia (latest-wins)" 0 run converged --repo repo-a
|
|
919
|
+
# ID inexistente = error de contrato
|
|
920
|
+
printf -- '{ "criteria": [ {"id": "RB-99", "verdict": "pass", "evidence": "x:1", "note": ""} ] }\n' > grader-bad.json
|
|
921
|
+
chk "ID inexistente en la rubrica -> exit 1" 1 \
|
|
922
|
+
run rubric-ingest --repo repo-a --report grader-bad.json --rubric RUBRIC.md
|
|
923
|
+
# negativo CON evidencia resta peso: 4/4 - 2 = 2/4 = 0.5
|
|
924
|
+
cat > grader-neg.json <<'EOF'
|
|
925
|
+
{ "criteria": [
|
|
926
|
+
{"id": "RB-01", "verdict": "pass", "evidence": "src/x.py:42 — ok", "note": "ok"},
|
|
927
|
+
{"id": "RB-02", "verdict": "pass", "evidence": "src/y.py:7 — ok", "note": "ok"},
|
|
928
|
+
{"id": "RB-NEG-01", "verdict": "fail", "evidence": "src/z.py:9 — 'now correctly...'", "note": "narra"} ] }
|
|
929
|
+
EOF
|
|
930
|
+
run rubric-ingest --repo repo-a --report grader-neg.json --rubric RUBRIC.md --json 2>/dev/null | "$PY" -c "
|
|
931
|
+
import json, sys
|
|
932
|
+
d = json.load(sys.stdin)
|
|
933
|
+
sys.exit(0 if d['verdict'] == 'BELOW' and abs(d['score'] - 0.5) < 0.001 else 1)" \
|
|
934
|
+
&& { PASS=$((PASS+1)); echo " ok negativo con evidencia resta peso (4-2)/4 = 0.50"; } \
|
|
935
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL semantica de negativos"; }
|
|
936
|
+
|
|
937
|
+
echo "== T45 anti-ceremonia (1.25.0): readiness = veredicto unico; --verbose expande; gates colapsados =="
|
|
938
|
+
printf '{ "defaults": { "acceptance_file": "ACCEPTANCE.md" },\n "repos": [ {"name":"solo","path":"repo-c","type":"python"} ], "integration": {"enabled": false} }\n' > ac-cfg.json
|
|
939
|
+
run init --config ac-cfg.json --out L-ac.json >/dev/null
|
|
940
|
+
# default = 1 veredicto, SIN la tabla de rutina (dimensiones/by-repo son ceremonia)
|
|
941
|
+
run readiness --ledger L-ac.json 2>/dev/null | grep -q "^READINESS:" \
|
|
942
|
+
&& { PASS=$((PASS+1)); echo " ok default emite el veredicto (READINESS:)"; } \
|
|
943
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL default no emite el veredicto"; }
|
|
944
|
+
run readiness --ledger L-ac.json 2>/dev/null | grep -q -- "--- dimensions" \
|
|
945
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL default filtra la tabla de dimensiones (ceremonia)"; } \
|
|
946
|
+
|| { PASS=$((PASS+1)); echo " ok default colapsa las dimensiones"; }
|
|
947
|
+
run readiness --ledger L-ac.json 2>/dev/null | grep -q -- "--- by repo" \
|
|
948
|
+
&& { FAIL=$((FAIL+1)); echo " FAIL default filtra el by-repo (ceremonia)"; } \
|
|
949
|
+
|| { PASS=$((PASS+1)); echo " ok default colapsa el by-repo"; }
|
|
950
|
+
# --verbose expande el detalle
|
|
951
|
+
run readiness --ledger L-ac.json --verbose 2>/dev/null | grep -q -- "--- dimensions" \
|
|
952
|
+
&& { PASS=$((PASS+1)); echo " ok --verbose expande las dimensiones"; } \
|
|
953
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL --verbose no expande las dimensiones"; }
|
|
954
|
+
run readiness --ledger L-ac.json --verbose 2>/dev/null | grep -q -- "--- by repo" \
|
|
955
|
+
&& { PASS=$((PASS+1)); echo " ok --verbose expande el by-repo"; } \
|
|
956
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL --verbose no expande el by-repo"; }
|
|
957
|
+
# un gate BLOQUEANTE persistido aparece nombrado en la linea colapsada
|
|
958
|
+
run log-gate --repo solo --iteration 1 --kind simplicity --verdict fail --count 3 --ledger L-ac.json >/dev/null 2>&1
|
|
959
|
+
run readiness --ledger L-ac.json 2>/dev/null | grep -- "--- gates:" | grep -q "solo/gate:simplicity" \
|
|
960
|
+
&& { PASS=$((PASS+1)); echo " ok gate bloqueante colapsado y nombrado"; } \
|
|
961
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL gate bloqueante no nombrado en la linea de gates"; }
|
|
962
|
+
# gate limpio (latest-wins) -> la linea reporta ninguno bloqueando
|
|
963
|
+
run log-gate --repo solo --iteration 2 --kind simplicity --verdict pass --ledger L-ac.json >/dev/null 2>&1
|
|
964
|
+
run readiness --ledger L-ac.json 2>/dev/null | grep -- "--- gates:" | grep -q "ninguno bloqueando" \
|
|
965
|
+
&& { PASS=$((PASS+1)); echo " ok gate limpio libera la linea de gates (latest-wins)"; } \
|
|
966
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL gate limpio no libera la linea de gates"; }
|
|
967
|
+
# --json expone gates[] (aditivo: presentacion sobre hechos, no recomputa el KPI)
|
|
968
|
+
run readiness --ledger L-ac.json --json 2>/dev/null | "$PY" -c "
|
|
969
|
+
import json, sys
|
|
970
|
+
d = json.load(sys.stdin)
|
|
971
|
+
g = d.get('gates')
|
|
972
|
+
ok = (isinstance(g, list) and len(g) == 1 and g[0]['tool'] == 'gate:simplicity'
|
|
973
|
+
and g[0]['blocking'] is False)
|
|
974
|
+
sys.exit(0 if ok else 1)" \
|
|
975
|
+
&& { PASS=$((PASS+1)); echo " ok --json expone gates[] aditivo (latest limpio, blocking False)"; } \
|
|
976
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL contrato gates[] en --json"; }
|
|
977
|
+
|
|
978
|
+
echo "== T46 REUSE-FIRST (1.26.0): waste-check Type-1/2 clon-vs-repo, advisory-first, determinista =="
|
|
979
|
+
mkdir -p wrepo/util
|
|
980
|
+
cat > wrepo/util/money.py <<'EOF'
|
|
981
|
+
def compute_total(items, rate):
|
|
982
|
+
subtotal = sum(x.price for x in items)
|
|
983
|
+
taxed = subtotal * (1 + rate)
|
|
984
|
+
shipping = 5 if taxed < 100 else 0
|
|
985
|
+
grand = round(taxed + shipping, 2)
|
|
986
|
+
return grand
|
|
987
|
+
EOF
|
|
988
|
+
# LEAN: archivo nuevo, codigo unico (no clona nada del repo)
|
|
989
|
+
cat > lean.diff <<'EOF'
|
|
990
|
+
diff --git a/service/report.py b/service/report.py
|
|
991
|
+
new file mode 100644
|
|
992
|
+
--- /dev/null
|
|
993
|
+
+++ b/service/report.py
|
|
994
|
+
@@ -0,0 +1,6 @@
|
|
995
|
+
+def build_report(rows, header):
|
|
996
|
+
+ lines = [header.upper(), "----------"]
|
|
997
|
+
+ for entry in rows:
|
|
998
|
+
+ lines.append(entry.render_line())
|
|
999
|
+
+ joined = "\n".join(lines)
|
|
1000
|
+
+ return joined
|
|
1001
|
+
EOF
|
|
1002
|
+
# CLON VS REPO: archivo nuevo que reimplementa wrepo/util/money.py exacto
|
|
1003
|
+
cat > clone.diff <<'EOF'
|
|
1004
|
+
diff --git a/service/checkout.py b/service/checkout.py
|
|
1005
|
+
new file mode 100644
|
|
1006
|
+
--- /dev/null
|
|
1007
|
+
+++ b/service/checkout.py
|
|
1008
|
+
@@ -0,0 +1,6 @@
|
|
1009
|
+
+def compute_total(items, rate):
|
|
1010
|
+
+ subtotal = sum(x.price for x in items)
|
|
1011
|
+
+ taxed = subtotal * (1 + rate)
|
|
1012
|
+
+ shipping = 5 if taxed < 100 else 0
|
|
1013
|
+
+ grand = round(taxed + shipping, 2)
|
|
1014
|
+
+ return grand
|
|
1015
|
+
EOF
|
|
1016
|
+
# CLON INTERNO: el mismo bloque repetido dentro del propio diff (no en el repo)
|
|
1017
|
+
cat > internal.diff <<'EOF'
|
|
1018
|
+
diff --git a/service/dup.py b/service/dup.py
|
|
1019
|
+
new file mode 100644
|
|
1020
|
+
--- /dev/null
|
|
1021
|
+
+++ b/service/dup.py
|
|
1022
|
+
@@ -0,0 +1,12 @@
|
|
1023
|
+
+def parse_alpha(text, mode):
|
|
1024
|
+
+ tokens = text.split(mode)
|
|
1025
|
+
+ cleaned = [t.strip() for t in tokens]
|
|
1026
|
+
+ filtered = [t for t in cleaned if t]
|
|
1027
|
+
+ counted = len(filtered)
|
|
1028
|
+
+ return counted, filtered
|
|
1029
|
+
+def parse_beta(text, mode):
|
|
1030
|
+
+ tokens = text.split(mode)
|
|
1031
|
+
+ cleaned = [t.strip() for t in tokens]
|
|
1032
|
+
+ filtered = [t for t in cleaned if t]
|
|
1033
|
+
+ counted = len(filtered)
|
|
1034
|
+
+ return counted, filtered
|
|
1035
|
+
EOF
|
|
1036
|
+
# CLON EN TEST: excluido (como simplicity) -> no cuenta
|
|
1037
|
+
cat > clone-test.diff <<'EOF'
|
|
1038
|
+
diff --git a/tests/test_checkout.py b/tests/test_checkout.py
|
|
1039
|
+
new file mode 100644
|
|
1040
|
+
--- /dev/null
|
|
1041
|
+
+++ b/tests/test_checkout.py
|
|
1042
|
+
@@ -0,0 +1,6 @@
|
|
1043
|
+
+def compute_total(items, rate):
|
|
1044
|
+
+ subtotal = sum(x.price for x in items)
|
|
1045
|
+
+ taxed = subtotal * (1 + rate)
|
|
1046
|
+
+ shipping = 5 if taxed < 100 else 0
|
|
1047
|
+
+ grand = round(taxed + shipping, 2)
|
|
1048
|
+
+ return grand
|
|
1049
|
+
EOF
|
|
1050
|
+
chk "lean.diff -> exit 0 (advisory)" 0 run waste-check --diff lean.diff --repo-root wrepo
|
|
1051
|
+
run waste-check --diff lean.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "
|
|
1052
|
+
import json, sys
|
|
1053
|
+
d = json.load(sys.stdin)
|
|
1054
|
+
sys.exit(0 if d['verdict'] == 'LEAN' and d['metrics']['dup_windows_vs_repo'] == 0 else 1)" \
|
|
1055
|
+
&& { PASS=$((PASS+1)); echo " ok codigo unico -> LEAN, 0 clones vs repo"; } \
|
|
1056
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL lean mal clasificado"; }
|
|
1057
|
+
chk "clon vs repo -> exit 0 SIN gate (advisory-first)" 0 run waste-check --diff clone.diff --repo-root wrepo
|
|
1058
|
+
run waste-check --diff clone.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "
|
|
1059
|
+
import json, sys
|
|
1060
|
+
d = json.load(sys.stdin)
|
|
1061
|
+
ok = (d['verdict'] == 'WASTEFUL' and d['metrics']['dup_windows_vs_repo'] >= 1
|
|
1062
|
+
and d['gate'] is False and any('money.py' in f for f in d['flags']))
|
|
1063
|
+
sys.exit(0 if ok else 1)" \
|
|
1064
|
+
&& { PASS=$((PASS+1)); echo " ok clon vs repo -> WASTEFUL + flag nombra el original (money.py)"; } \
|
|
1065
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL clon vs repo no detectado"; }
|
|
1066
|
+
chk "clon vs repo con --gate -> exit 1 (BLOCKER declarado)" 1 run waste-check --diff clone.diff --repo-root wrepo --gate
|
|
1067
|
+
run waste-check --diff internal.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "
|
|
1068
|
+
import json, sys
|
|
1069
|
+
d = json.load(sys.stdin)
|
|
1070
|
+
sys.exit(0 if d['metrics']['dup_windows_internal'] >= 1 else 1)" \
|
|
1071
|
+
&& { PASS=$((PASS+1)); echo " ok clon interno detectado (dup_windows_internal >= 1)"; } \
|
|
1072
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL clon interno no detectado"; }
|
|
1073
|
+
run waste-check --diff clone-test.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "
|
|
1074
|
+
import json, sys
|
|
1075
|
+
d = json.load(sys.stdin)
|
|
1076
|
+
sys.exit(0 if d['verdict'] == 'LEAN' and d['metrics']['dup_windows_vs_repo'] == 0 else 1)" \
|
|
1077
|
+
&& { PASS=$((PASS+1)); echo " ok clon en archivo de test EXCLUIDO (como simplicity)"; } \
|
|
1078
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL test no excluido del waste-check"; }
|
|
1079
|
+
# SELF-MATCH: un diff que TOCA util/money.py y le agrega una copia de su PROPIO bloque
|
|
1080
|
+
# no debe matchearse contra si mismo (el archivo tocado se excluye del escaneo del repo)
|
|
1081
|
+
cat > selfmod.diff <<'EOF'
|
|
1082
|
+
diff --git a/util/money.py b/util/money.py
|
|
1083
|
+
--- a/util/money.py
|
|
1084
|
+
+++ b/util/money.py
|
|
1085
|
+
@@ -6,0 +7,6 @@
|
|
1086
|
+
+def compute_total(items, rate):
|
|
1087
|
+
+ subtotal = sum(x.price for x in items)
|
|
1088
|
+
+ taxed = subtotal * (1 + rate)
|
|
1089
|
+
+ shipping = 5 if taxed < 100 else 0
|
|
1090
|
+
+ grand = round(taxed + shipping, 2)
|
|
1091
|
+
+ return grand
|
|
1092
|
+
EOF
|
|
1093
|
+
run waste-check --diff selfmod.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "
|
|
1094
|
+
import json, sys
|
|
1095
|
+
d = json.load(sys.stdin)
|
|
1096
|
+
sys.exit(0 if d['metrics']['dup_windows_vs_repo'] == 0 else 1)" \
|
|
1097
|
+
&& { PASS=$((PASS+1)); echo " ok archivo tocado excluido del escaneo (no auto-match)"; } \
|
|
1098
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL self-match: el archivo tocado se comparo consigo mismo"; }
|
|
1099
|
+
# determinismo: misma entrada -> mismo score (sin azar, sin red, sin LLM)
|
|
1100
|
+
W1=$(run waste-check --diff clone.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "import json,sys;print(json.load(sys.stdin)['score'])")
|
|
1101
|
+
W2=$(run waste-check --diff clone.diff --repo-root wrepo --json 2>/dev/null | "$PY" -c "import json,sys;print(json.load(sys.stdin)['score'])")
|
|
1102
|
+
[ "$W1" = "$W2" ] && [ -n "$W1" ] \
|
|
1103
|
+
&& { PASS=$((PASS+1)); echo " ok determinista (score $W1 estable)"; } \
|
|
1104
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL no determinista ($W1 vs $W2)"; }
|
|
1105
|
+
# log-gate --kind waste persiste y entra al rollup de readiness (1.25.0)
|
|
1106
|
+
run log-gate --repo solo --iteration 3 --kind waste --verdict fail --count 2 --ledger L-ac.json >/dev/null 2>&1
|
|
1107
|
+
run readiness --ledger L-ac.json 2>/dev/null | grep -- "--- gates:" | grep -q "gate:waste" \
|
|
1108
|
+
&& { PASS=$((PASS+1)); echo " ok gate:waste persistido aparece en el veredicto unico"; } \
|
|
1109
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL gate:waste no entra al rollup de readiness"; }
|
|
1110
|
+
|
|
1111
|
+
echo "== T47 FTY (1.27.0): first-time yield pasivo en summary, informativo, no gatea =="
|
|
1112
|
+
printf '{ "defaults": {"qa_tools_order":["code-review"]},\n "repos": [ {"name":"fa","path":"repo-c","type":"python"},{"name":"fb","path":"repo-d","type":"python"} ], "integration": {"enabled": false} }\n' > fty-cfg.json
|
|
1113
|
+
run init --config fty-cfg.json --out L-fty.json >/dev/null
|
|
1114
|
+
# fa: limpio al ciclo 1 (first-time). fb: necesito 2 ciclos (no first-time).
|
|
1115
|
+
run log-step --repo fa --tool code-review --iteration 1 --gated-reported 0 --tests-passed true --ledger L-fty.json >/dev/null 2>&1
|
|
1116
|
+
run log-step --repo fb --tool code-review --iteration 1 --gated-reported 3 --tests-passed true --ledger L-fty.json >/dev/null 2>&1
|
|
1117
|
+
run log-step --repo fb --tool code-review --iteration 2 --gated-reported 0 --tests-passed true --ledger L-fty.json >/dev/null 2>&1
|
|
1118
|
+
run summary --ledger L-fty.json --json 2>/dev/null | "$PY" -c "
|
|
1119
|
+
import json, sys
|
|
1120
|
+
f = json.load(sys.stdin)['first_time_yield']
|
|
1121
|
+
ok = (f['repos_through_qa'] == 2 and f['repos_first_time'] == 1 and f['pct'] == 50.0)
|
|
1122
|
+
sys.exit(0 if ok else 1)" \
|
|
1123
|
+
&& { PASS=$((PASS+1)); echo " ok FTY 50% (fa limpio al 1er ciclo, fb necesito 2)"; } \
|
|
1124
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL FTY mal computado"; }
|
|
1125
|
+
run summary --ledger L-fty.json 2>/dev/null | grep -q "first-time yield: 50.0%" \
|
|
1126
|
+
&& { PASS=$((PASS+1)); echo " ok FTY visible en el texto del summary"; } \
|
|
1127
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL FTY no aparece en summary"; }
|
|
1128
|
+
# una escalacion (aunque el repo converja) lo saca del first-time yield
|
|
1129
|
+
run escalate --repo fa --reason "design doubt" --ledger L-fty.json >/dev/null 2>&1
|
|
1130
|
+
run summary --ledger L-fty.json --json 2>/dev/null | "$PY" -c "
|
|
1131
|
+
import json, sys
|
|
1132
|
+
f = json.load(sys.stdin)['first_time_yield']
|
|
1133
|
+
sys.exit(0 if f['repos_first_time'] == 0 and f['pct'] == 0.0 else 1)" \
|
|
1134
|
+
&& { PASS=$((PASS+1)); echo " ok escalacion saca al repo del first-time yield"; } \
|
|
1135
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL escalacion no afecta FTY"; }
|
|
1136
|
+
|
|
1137
|
+
echo "== T48 pit-check: mutation gate desde un mutations.xml (efectividad, no coverage) =="
|
|
1138
|
+
cat > mutations.xml <<'EOF'
|
|
1139
|
+
<mutations>
|
|
1140
|
+
<mutation detected="true" status="KILLED"><sourceFile>a.py</sourceFile></mutation>
|
|
1141
|
+
<mutation detected="true" status="KILLED"><sourceFile>a.py</sourceFile></mutation>
|
|
1142
|
+
<mutation detected="true" status="KILLED"><sourceFile>a.py</sourceFile></mutation>
|
|
1143
|
+
<mutation detected="true" status="SURVIVED"><sourceFile>a.py</sourceFile></mutation>
|
|
1144
|
+
<mutation detected="false" status="SURVIVED"><sourceFile>b.py</sourceFile></mutation>
|
|
1145
|
+
<mutation detected="false" status="NO_COVERAGE"><sourceFile>b.py</sourceFile></mutation>
|
|
1146
|
+
<mutation detected="false" status="NON_VIABLE"><sourceFile>c.py</sourceFile></mutation>
|
|
1147
|
+
<mutation detected="false" status="RUN_ERROR"><sourceFile>c.py</sourceFile></mutation>
|
|
1148
|
+
</mutations>
|
|
1149
|
+
EOF
|
|
1150
|
+
# total = killed(4: 3 KILLED + 1 detected-override) + survived(1) + no_cov(1) = 6
|
|
1151
|
+
# NON_VIABLE + RUN_ERROR excluidos del denominador -> excluded=2
|
|
1152
|
+
# mutation_score = 100*4/6 = 66.7 ; test_strength = 100*4/(4+1) = 80.0
|
|
1153
|
+
chk "score 66.7 >= min-score 60 -> PASS exit 0" 0 run pit-check --report mutations.xml --min-score 60
|
|
1154
|
+
chk "score 66.7 < min-score 70 -> BELOW-GATE exit 1" 1 run pit-check --report mutations.xml --min-score 70
|
|
1155
|
+
run pit-check --report mutations.xml --min-score 60 --json 2>/dev/null | "$PY" -c "
|
|
1156
|
+
import json, sys
|
|
1157
|
+
m = json.load(sys.stdin)['metrics']
|
|
1158
|
+
ok = (m['total'] == 6 and m['killed'] == 4 and m['survived'] == 1
|
|
1159
|
+
and m['no_coverage'] == 1 and m['excluded'] == 2
|
|
1160
|
+
and abs(m['mutation_score'] - 66.7) < 0.05
|
|
1161
|
+
and abs(m['test_strength'] - 80.0) < 0.05)
|
|
1162
|
+
sys.exit(0 if ok else 1)" \
|
|
1163
|
+
&& { PASS=$((PASS+1)); echo " ok NON_VIABLE/RUN_ERROR fuera del denominador; detected=true cuenta como killed"; } \
|
|
1164
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL metricas del mutation report mal computadas"; }
|
|
1165
|
+
chk "report inexistente -> exit 2 (no evidencia)" 2 run pit-check --report no-such.xml --min-score 60
|
|
1166
|
+
|
|
1167
|
+
echo "== T49 check-coverage: gate de umbral (OK / BELOW / sin report = fail-closed) =="
|
|
1168
|
+
printf '{ "defaults": { "acceptance_file": "ACCEPTANCE.md" },\n "repos": [ {"name":"cov","path":"covrepo","type":"python"}, {"name":"nocov","path":"nocovrepo","type":"python"} ], "integration": {"enabled": false} }\n' > cc-cfg.json
|
|
1169
|
+
mkdir -p covrepo nocovrepo
|
|
1170
|
+
# Cobertura: line-rate 0.85 -> 85% (lines-valid/lines-covered coherentes con line-rate)
|
|
1171
|
+
cat > covrepo/coverage.xml <<'EOF'
|
|
1172
|
+
<?xml version="1.0"?>
|
|
1173
|
+
<coverage lines-valid="20" lines-covered="17" line-rate="0.85" version="7.4"></coverage>
|
|
1174
|
+
EOF
|
|
1175
|
+
run init --config cc-cfg.json --out L-cc.json >/dev/null
|
|
1176
|
+
chk "coverage 85% >= threshold 60 -> OK exit 0" 0 run check-coverage --repo cov --threshold 60 --ledger L-cc.json
|
|
1177
|
+
chk "coverage 85% < threshold 90 -> BELOW exit 1" 1 run check-coverage --repo cov --threshold 90 --ledger L-cc.json
|
|
1178
|
+
chk "sin report de coverage -> fail-closed exit 1" 1 run check-coverage --repo nocov --threshold 60 --ledger L-cc.json
|
|
1179
|
+
|
|
1180
|
+
echo "== T50 rebuild: baseline escribe la firma; compare puntua COVERS / DIVERGE =="
|
|
1181
|
+
mkdir -p rbrepo/src rbrepo/reports
|
|
1182
|
+
cat > rbrepo/src/mod.py <<'EOF'
|
|
1183
|
+
def add(a, b):
|
|
1184
|
+
return a + b
|
|
1185
|
+
def mul(a, b):
|
|
1186
|
+
return a * b
|
|
1187
|
+
EOF
|
|
1188
|
+
cat > rbrepo/coverage.xml <<'EOF'
|
|
1189
|
+
<?xml version="1.0"?>
|
|
1190
|
+
<coverage lines-valid="10" lines-covered="8" line-rate="0.8" version="7.4"></coverage>
|
|
1191
|
+
EOF
|
|
1192
|
+
cat > rbrepo/reports/junit.xml <<'EOF'
|
|
1193
|
+
<testsuites><testsuite name="S" tests="5" failures="0" errors="0" skipped="0"/></testsuites>
|
|
1194
|
+
EOF
|
|
1195
|
+
printf -- "# ACCEPTANCE\n\n- [x] uno\n- [x] dos\n" > ACCEPTANCE-rb.md
|
|
1196
|
+
printf '{ "defaults": { "acceptance_file": "ACCEPTANCE-rb.md" },\n "repos": [ {"name":"rb","path":"rbrepo","type":"python"} ], "integration": {"enabled": false} }\n' > rb-cfg.json
|
|
1197
|
+
# baseline: escribe la firma
|
|
1198
|
+
chk "baseline escribe REBUILD-BASELINE -> exit 0" 0 run rebuild --mode baseline --config rb-cfg.json --out RB.json
|
|
1199
|
+
"$PY" -c "import json,sys; d=json.load(open('RB.json',encoding='utf-8')); sys.exit(0 if 'rb' in d['repos'] and d['repos']['rb']['tests']['total']==5 else 1)" \
|
|
1200
|
+
&& { PASS=$((PASS+1)); echo " ok la firma baseline capturo el repo (tests total=5)"; } \
|
|
1201
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL baseline no capturo la firma esperada"; }
|
|
1202
|
+
# compare sobre el MISMO arbol -> todas las dimensiones 1.0 -> COVERS
|
|
1203
|
+
chk "compare mismo arbol -> COVERS exit 0" 0 run rebuild --mode compare --baseline RB.json --json
|
|
1204
|
+
run rebuild --mode compare --baseline RB.json --json 2>/dev/null | "$PY" -c "
|
|
1205
|
+
import json, sys
|
|
1206
|
+
d = json.load(sys.stdin)
|
|
1207
|
+
sys.exit(0 if d['verdict'] == 'COVERS' and d['dimensions']['tests'] == 1.0 else 1)" \
|
|
1208
|
+
&& { PASS=$((PASS+1)); echo " ok arbol sin cambios -> COVERS (tests dim 1.0)"; } \
|
|
1209
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL compare del mismo arbol no da COVERS"; }
|
|
1210
|
+
# el arbol 'regenerado' rompe tests -> la dimension dominante cae -> NO COVERS + gap
|
|
1211
|
+
cat > rbrepo/reports/junit.xml <<'EOF'
|
|
1212
|
+
<testsuites><testsuite name="S" tests="5" failures="4" errors="0" skipped="0"/></testsuites>
|
|
1213
|
+
EOF
|
|
1214
|
+
chk "compare con tests que fallan -> exit 1 (no COVERS)" 1 run rebuild --mode compare --baseline RB.json --json
|
|
1215
|
+
run rebuild --mode compare --baseline RB.json --json 2>/dev/null | "$PY" -c "
|
|
1216
|
+
import json, sys
|
|
1217
|
+
d = json.load(sys.stdin)
|
|
1218
|
+
ok = (d['verdict'] != 'COVERS' and d['dimensions']['tests'] < 0.5
|
|
1219
|
+
and any('fail' in g for g in d['gaps']))
|
|
1220
|
+
sys.exit(0 if ok else 1)" \
|
|
1221
|
+
&& { PASS=$((PASS+1)); echo " ok tests rotos al regenerar -> DIVERGE/PARTIAL + gap reportado"; } \
|
|
1222
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL compare no detecto la divergencia de tests"; }
|
|
1223
|
+
|
|
1224
|
+
echo "== T44 sync quintuple de version: VERSION = config = plugin.json = marketplace.json =="
|
|
1225
|
+
"$PY" -c "
|
|
1226
|
+
import json, sys, os, io
|
|
1227
|
+
kit = os.path.dirname(os.path.dirname(os.path.dirname(sys.argv[1]))) # <kit>/.claude/skills/x -> <kit>
|
|
1228
|
+
repo = os.path.dirname(kit)
|
|
1229
|
+
v_file = io.open(os.path.join(kit, 'VERSION'), encoding='utf-8').read().split()[-1]
|
|
1230
|
+
v_cfg = json.load(io.open(os.path.join(kit, 'uscha.config.json'), encoding='utf-8'))['version']
|
|
1231
|
+
v_plug = json.load(io.open(os.path.join(kit, '.claude-plugin', 'plugin.json'), encoding='utf-8'))['version']
|
|
1232
|
+
mk = json.load(io.open(os.path.join(repo, '.claude-plugin', 'marketplace.json'), encoding='utf-8'))
|
|
1233
|
+
v_mkt = mk['plugins'][0]['version']
|
|
1234
|
+
vs = {v_file, v_cfg, v_plug, v_mkt}
|
|
1235
|
+
print(' versiones:', v_file, v_cfg, v_plug, v_mkt)
|
|
1236
|
+
sys.exit(0 if len(vs) == 1 else 1)" "$(dirname "$QL")" \
|
|
1237
|
+
&& { PASS=$((PASS+1)); echo " ok las cuatro fuentes de version coinciden"; } \
|
|
1238
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL drift de version entre VERSION/config/plugin/marketplace"; }
|
|
1239
|
+
|
|
1240
|
+
echo "== T51 freshness (1.31.0): reporte JUnit mas viejo que el codigo = STALE -> AC UNMEASURED =="
|
|
1241
|
+
mkdir -p repo-fresh/reports
|
|
1242
|
+
printf 'def alta():\n return True\n' > repo-fresh/alta.py
|
|
1243
|
+
cat > repo-fresh/reports/junit.xml <<'EOF'
|
|
1244
|
+
<?xml version="1.0"?>
|
|
1245
|
+
<testsuites><testsuite name="pytest" tests="1" failures="0" errors="0" skipped="0">
|
|
1246
|
+
<testcase classname="tests.test_flow" name="test_ac1_alta_ok"/>
|
|
1247
|
+
</testsuite></testsuites>
|
|
1248
|
+
EOF
|
|
1249
|
+
printf -- "# ACCEPTANCE\n\n- [x] AC-01 alta\n" > acc-fresh.md
|
|
1250
|
+
printf '{ "defaults": { "acceptance_file": "acc-fresh.md" },\n "repos": [ {"name":"fresh","path":"repo-fresh","type":"python"} ], "integration": {"enabled": false} }\n' > fresh.json
|
|
1251
|
+
run init --config fresh.json --out L-fresh.json >/dev/null 2>&1
|
|
1252
|
+
# FRESCO: reporte mas nuevo que la fuente -> AC-1 cierra medido, sin stale
|
|
1253
|
+
touch -t 202601010800 repo-fresh/alta.py
|
|
1254
|
+
touch -t 202601010900 repo-fresh/reports/junit.xml
|
|
1255
|
+
run readiness --ledger L-fresh.json --json 2>/dev/null | "$PY" -c "
|
|
1256
|
+
import json, sys
|
|
1257
|
+
a = json.load(sys.stdin)['acceptance']
|
|
1258
|
+
sys.exit(0 if a['measured_closed'] == ['AC-1'] and a['stale_reports'] == [] else 1)" \
|
|
1259
|
+
&& { PASS=$((PASS+1)); echo " ok reporte fresco: AC-1 cierra medido, stale_reports vacio"; } \
|
|
1260
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL fresco no cierra o reporta stale de mas (falso positivo)"; }
|
|
1261
|
+
# STALE: la fuente pasa a ser mas nueva que el reporte -> reporte descartado
|
|
1262
|
+
touch -t 202601011200 repo-fresh/alta.py
|
|
1263
|
+
run readiness --ledger L-fresh.json --json 2>/dev/null | "$PY" -c "
|
|
1264
|
+
import json, sys
|
|
1265
|
+
a = json.load(sys.stdin)['acceptance']
|
|
1266
|
+
sys.exit(0 if a['measured_closed'] == [] and len(a['stale_reports']) == 1 else 1)" \
|
|
1267
|
+
&& { PASS=$((PASS+1)); echo " ok codigo mas nuevo: reporte STALE descartado, AC-1 UNMEASURED"; } \
|
|
1268
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL stale no descartado (falso-verde por evidencia vieja)"; }
|
|
1269
|
+
run readiness --ledger L-fresh.json 2>/dev/null | grep -q "STALE descartados" \
|
|
1270
|
+
&& { PASS=$((PASS+1)); echo " ok advisory de reportes STALE visible en la vista default"; } \
|
|
1271
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL sin advisory de STALE"; }
|
|
1272
|
+
|
|
1273
|
+
echo "== T52 gate de doc-version (1.31.0): READMEs (marcador uscha:version) = VERSION =="
|
|
1274
|
+
"$PY" -c "
|
|
1275
|
+
import sys, os, io, re
|
|
1276
|
+
skdir = sys.argv[1] # <kit>/.claude/skills/uscha-devloop
|
|
1277
|
+
kit = os.path.dirname(os.path.dirname(os.path.dirname(skdir)))
|
|
1278
|
+
repo = os.path.dirname(kit)
|
|
1279
|
+
ver = io.open(os.path.join(kit, 'VERSION'), encoding='utf-8').read().split()[-1]
|
|
1280
|
+
docs = [os.path.join(repo, 'README.md'), os.path.join(kit, 'README.md')]
|
|
1281
|
+
rx = re.compile(r'v?(\d+\.\d+\.\d+)')
|
|
1282
|
+
bad = []
|
|
1283
|
+
for d in docs:
|
|
1284
|
+
marked = [l for l in io.open(d, encoding='utf-8') if 'uscha:version' in l]
|
|
1285
|
+
if not marked:
|
|
1286
|
+
bad.append(d + ': SIN marcador uscha:version'); continue
|
|
1287
|
+
m = rx.search(marked[0])
|
|
1288
|
+
got = m.group(1) if m else None
|
|
1289
|
+
if got != ver:
|
|
1290
|
+
bad.append(d + ': marcador dice ' + str(got) + ' != VERSION ' + ver)
|
|
1291
|
+
print(' VERSION:', ver, '· READMEs marcados y en sync:', len(docs) - len(bad))
|
|
1292
|
+
for b in bad: print(' ', b)
|
|
1293
|
+
sys.exit(1 if bad else 0)" "$(dirname "$QL")" \
|
|
1294
|
+
&& { PASS=$((PASS+1)); echo " ok los READMEs declaran la version actual (doc drift bloqueado)"; } \
|
|
1295
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL doc-version drift: un README no coincide con VERSION"; }
|
|
1296
|
+
|
|
1297
|
+
echo "== T53 dashboard (1.32.0): contrato mirador desde el ledger, truth-pass (null sin fuente) =="
|
|
1298
|
+
mkdir -p repo-mir/reports docs/adr-mir
|
|
1299
|
+
printf 'def a():\n return 1\n' > repo-mir/x.py
|
|
1300
|
+
printf -- "# ADR-001 Append-only\n\nStatus: accepted\n" > docs/adr-mir/ADR-001.md
|
|
1301
|
+
printf -- "# ADR-002 Rollback\n\nStatus: proposed\n" > docs/adr-mir/ADR-002.md
|
|
1302
|
+
printf -- "# ACCEPTANCE\n\n- [x] AC-01 alta\n" > acc-mir.md
|
|
1303
|
+
printf '{ "defaults": { "acceptance_file": "acc-mir.md" },\n "repos": [ {"name":"backend-api","path":"repo-mir","type":"python"} ], "integration": {"enabled": false} }\n' > mir.json
|
|
1304
|
+
run init --config mir.json --out L-mir.json >/dev/null 2>&1
|
|
1305
|
+
run dashboard --ledger L-mir.json --adr-dir docs/adr-mir --json 2>/dev/null | "$PY" -c "
|
|
1306
|
+
import json, sys
|
|
1307
|
+
d = json.load(sys.stdin)
|
|
1308
|
+
keys = ['project','generated','readiness','subscores','phases','specs','adrs','inv','capas','loops','snapshots','evidence']
|
|
1309
|
+
assert all(k in d for k in keys), 'faltan claves: ' + str([k for k in keys if k not in d])
|
|
1310
|
+
assert isinstance(d['readiness']['score'], (int, float)), 'readiness.score'
|
|
1311
|
+
assert d['specs'] == [] and d['capas'] == [], 'specs/capas deben ser [] (truth-pass, sin fuente)'
|
|
1312
|
+
assert len(d['adrs']) == 2 and d['adrs'][0]['status'] == 'done' and d['adrs'][1]['status'] == 'prog', 'adrs glob'
|
|
1313
|
+
assert d['snapshots'] == [], 'snapshots vacio antes de --record'
|
|
1314
|
+
assert len(d['phases']) == 8 and len(d['inv']) == 7, 'esqueleto phases(8)/inv(7)'
|
|
1315
|
+
sys.exit(0)" \
|
|
1316
|
+
&& { PASS=$((PASS+1)); echo " ok contrato completo; specs/capas []; adrs del glob; snapshots vacio pre-record"; } \
|
|
1317
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL contrato dashboard mal formado"; }
|
|
1318
|
+
DS=$(run dashboard --ledger L-mir.json --adr-dir docs/adr-mir --json 2>/dev/null | "$PY" -c "import json,sys;print(json.load(sys.stdin)['readiness']['score'])")
|
|
1319
|
+
RS=$(run readiness --ledger L-mir.json --json 2>/dev/null | "$PY" -c "import json,sys;print(json.load(sys.stdin)['score'])")
|
|
1320
|
+
[ "$DS" = "$RS" ] \
|
|
1321
|
+
&& { PASS=$((PASS+1)); echo " ok readiness del dashboard == readiness --json ($DS, reuso verbatim sin drift)"; } \
|
|
1322
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL drift readiness dashboard($DS) vs readiness($RS)"; }
|
|
1323
|
+
run readiness --ledger L-mir.json --record >/dev/null 2>&1
|
|
1324
|
+
run dashboard --ledger L-mir.json --adr-dir docs/adr-mir --json 2>/dev/null | "$PY" -c "
|
|
1325
|
+
import json, sys
|
|
1326
|
+
s = json.load(sys.stdin)['snapshots']
|
|
1327
|
+
sys.exit(0 if len(s) == 1 and isinstance(s[0]['readiness'], (int, float)) and 'date' in s[0] else 1)" \
|
|
1328
|
+
&& { PASS=$((PASS+1)); echo " ok readiness --record puebla el time-lapse (add-on prospectivo)"; } \
|
|
1329
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL time-lapse no se poblo tras --record"; }
|
|
1330
|
+
# inv mapea el gate persistido por su kind REAL (pit-check, no 'pit'): sin este check
|
|
1331
|
+
# un typo de kind deja el invariante en null en silencio (regresion muda).
|
|
1332
|
+
run log-gate --ledger L-mir.json --repo backend-api --iteration 1 --kind pit-check --verdict fail --count 2 >/dev/null 2>&1
|
|
1333
|
+
run log-gate --ledger L-mir.json --repo backend-api --iteration 1 --kind simplicity --verdict pass >/dev/null 2>&1
|
|
1334
|
+
run dashboard --ledger L-mir.json --adr-dir docs/adr-mir --json 2>/dev/null | "$PY" -c "
|
|
1335
|
+
import json, sys
|
|
1336
|
+
inv = {i['name']: i['status'] for i in json.load(sys.stdin)['inv']}
|
|
1337
|
+
sys.exit(0 if inv.get('Tests efectivos') == 'miss' and inv.get('Simplicidad') == 'ok' else 1)" \
|
|
1338
|
+
&& { PASS=$((PASS+1)); echo " ok inv mapea el gate por su kind real (pit-check->Tests efectivos miss, simplicity->ok)"; } \
|
|
1339
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL inv no mapea el gate persistido (kind mal escrito?)"; }
|
|
1340
|
+
|
|
1341
|
+
echo "== T54 telemetry-extract (1.33.0): transcript CC -> sidecar (vendor adapter, FUERA del engine) =="
|
|
1342
|
+
EXTRACT="$(dirname "$(dirname "$QL")")/uscha-mirador/telemetry-extract.py"
|
|
1343
|
+
mkdir -p tele
|
|
1344
|
+
cat > tele/t.jsonl <<'EOF'
|
|
1345
|
+
{"type":"assistant","timestamp":"2026-07-05T20:00:00Z","message":{"model":"claude-opus-4-8","usage":{"input_tokens":10000,"cache_read_input_tokens":40000,"output_tokens":3000}}}
|
|
1346
|
+
{"type":"assistant","timestamp":"2026-07-05T20:10:00Z","message":{"model":"claude-haiku-4-5","usage":{"input_tokens":4000,"output_tokens":1500}}}
|
|
1347
|
+
{bad json se saltea}
|
|
1348
|
+
EOF
|
|
1349
|
+
"$PY" "$EXTRACT" tele/t.jsonl --sidecar tele/telemetry.jsonl >/dev/null 2>&1
|
|
1350
|
+
"$PY" -c "
|
|
1351
|
+
import json, sys
|
|
1352
|
+
d = json.loads(open('tele/telemetry.jsonl', encoding='utf-8').readline())
|
|
1353
|
+
ok = (d['tokens_in'] == 54000 and d['tokens_out'] == 4500 and d['ms'] == 600000
|
|
1354
|
+
and len(d['by_model']) == 2
|
|
1355
|
+
and {m['model'] for m in d['by_model']} == {'claude-opus-4-8', 'claude-haiku-4-5'})
|
|
1356
|
+
sys.exit(0 if ok else 1)" \
|
|
1357
|
+
&& { PASS=$((PASS+1)); echo " ok suma tokens (input+cache) + wall time + by_model desde el transcript"; } \
|
|
1358
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL extractor no resume bien el transcript CC"; }
|
|
1359
|
+
# upsert idempotente (1.34.0): re-correr con la misma sesion NO duplica la linea
|
|
1360
|
+
"$PY" "$EXTRACT" tele/t.jsonl --sidecar tele/telemetry.jsonl >/dev/null 2>&1
|
|
1361
|
+
"$PY" -c "
|
|
1362
|
+
import sys
|
|
1363
|
+
n = sum(1 for l in open('tele/telemetry.jsonl', encoding='utf-8') if l.strip())
|
|
1364
|
+
sys.exit(0 if n == 1 else 1)" \
|
|
1365
|
+
&& { PASS=$((PASS+1)); echo " ok re-correr el extractor hace UPSERT (1 linea, no infla el total en watch)"; } \
|
|
1366
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL el extractor duplico la linea de sesion (watch-mode inflaria)"; }
|
|
1367
|
+
|
|
1368
|
+
echo "== T55 dashboard project (1.34.0): config.project gana; sin el, join de repos =="
|
|
1369
|
+
printf '{ "project": "My Project", "defaults": { "acceptance_file": "acc-mir.md" },\n "repos": [ {"name":"backend-api","path":"repo-mir","type":"python"} ], "integration": {"enabled": false} }\n' > mirp.json
|
|
1370
|
+
run init --config mirp.json --out L-mirp.json >/dev/null 2>&1
|
|
1371
|
+
run dashboard --ledger L-mirp.json --json 2>/dev/null | "$PY" -c "import json,sys; sys.exit(0 if json.load(sys.stdin)['project']=='My Project' else 1)" \
|
|
1372
|
+
&& { PASS=$((PASS+1)); echo " ok config.project -> dashboard.project"; } \
|
|
1373
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL no toma project del config"; }
|
|
1374
|
+
run dashboard --ledger L-mir.json --json 2>/dev/null | "$PY" -c "import json,sys; sys.exit(0 if json.load(sys.stdin)['project']=='backend-api' else 1)" \
|
|
1375
|
+
&& { PASS=$((PASS+1)); echo " ok sin project en config -> fallback al nombre del repo (truth-pass)"; } \
|
|
1376
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL fallback de project mal"; }
|
|
1377
|
+
|
|
1378
|
+
echo "== T56 mirador-render (1.34.0): dashboard + telemetria mergeada + inject + meta-refresh =="
|
|
1379
|
+
RENDER="$(dirname "$(dirname "$QL")")/uscha-mirador/mirador-render.py"
|
|
1380
|
+
TPL="$(dirname "$(dirname "$QL")")/uscha-mirador/mirador.template.html"
|
|
1381
|
+
"$PY" "$RENDER" --engine "$QL" --ledger L-mirp.json --template "$TPL" --out mir-out.html --sidecar tele/telemetry.jsonl --refresh 30 >/dev/null 2>&1
|
|
1382
|
+
"$PY" -c "
|
|
1383
|
+
import re, json, sys
|
|
1384
|
+
h = open('mir-out.html', encoding='utf-8').read()
|
|
1385
|
+
m = re.search(r'const DATA = (\{.*\});\n/\*MIRADOR_DATA_END', h, re.S)
|
|
1386
|
+
d = json.loads(m.group(1))
|
|
1387
|
+
ok = (d['project'] == 'My Project' and 'telemetry' in d
|
|
1388
|
+
and d['telemetry']['tokens_in'] == 54000
|
|
1389
|
+
and 'http-equiv=\"refresh\" content=\"30\"' in h)
|
|
1390
|
+
sys.exit(0 if ok else 1)" \
|
|
1391
|
+
&& { PASS=$((PASS+1)); echo " ok render standalone: project + telemetria + meta-refresh en mirador.html"; } \
|
|
1392
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL mirador-render no produjo el HTML esperado"; }
|
|
1393
|
+
|
|
1394
|
+
echo "== T57 skill-count no-drift (1.34.0): USCHA_SKILLS del doctor == dirs uscha-* en disco =="
|
|
1395
|
+
SKILLS_DIR="$(dirname "$(dirname "$QL")")"
|
|
1396
|
+
"$PY" -c "
|
|
1397
|
+
import sys, os
|
|
1398
|
+
sys.path.insert(0, os.path.dirname(sys.argv[1]))
|
|
1399
|
+
import qa_ledger as q
|
|
1400
|
+
listed = set(q.USCHA_SKILLS)
|
|
1401
|
+
ondisk = {d for d in os.listdir(sys.argv[2])
|
|
1402
|
+
if d.startswith('uscha-') and os.path.isdir(os.path.join(sys.argv[2], d))}
|
|
1403
|
+
sys.exit(0 if listed == ondisk else 1)" "$QL" "$SKILLS_DIR" \
|
|
1404
|
+
&& { PASS=$((PASS+1)); echo " ok el doctor lista exactamente las skills uscha-* en disco (sin drift)"; } \
|
|
1405
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL USCHA_SKILLS != dirs uscha-* en disco (skill nueva sin registrar en el doctor?)"; }
|
|
1406
|
+
|
|
1407
|
+
echo "== T58 execution-policy (1.35.0): routing por fase sin contaminar readiness =="
|
|
1408
|
+
cat > ep.json <<'EOF'
|
|
1409
|
+
{ "version": "1.35.0",
|
|
1410
|
+
"defaults": {
|
|
1411
|
+
"execution_policy": {
|
|
1412
|
+
"default": { "tier": "standard", "effort": "medium" },
|
|
1413
|
+
"phases": {
|
|
1414
|
+
"qa": { "method": "checker fresco", "tier": "checker", "model": "gpt-5.5", "effort": "high", "uncorrelated": true },
|
|
1415
|
+
"build": { "method": "implementar plan", "tier": "standard", "effort": "medium" }
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
"repos": [ {"name":"repo-c","path":"repo-c","type":"python"} ],
|
|
1420
|
+
"integration": {"enabled": false} }
|
|
1421
|
+
EOF
|
|
1422
|
+
run init --config ep.json --out L-ep.json >/dev/null 2>&1
|
|
1423
|
+
run execution-policy --ledger L-ep.json --phase qa --json 2>/dev/null | "$PY" -c "
|
|
1424
|
+
import json, sys
|
|
1425
|
+
d = json.load(sys.stdin)
|
|
1426
|
+
ok = (d['phase'] == 'qa' and d['method'] == 'checker fresco'
|
|
1427
|
+
and d['tier'] == 'checker' and d['model'] == 'gpt-5.5'
|
|
1428
|
+
and d['effort'] == 'high' and d['uncorrelated'] is True)
|
|
1429
|
+
sys.exit(0 if ok else 1)" \
|
|
1430
|
+
&& { PASS=$((PASS+1)); echo " ok CLI JSON devuelve metodologia/model/effort declarados para qa"; } \
|
|
1431
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL execution-policy --json no respeta config.defaults.execution_policy"; }
|
|
1432
|
+
run execution-policy --ledger L-ep.json --phase qa 2>/dev/null | grep -q "EXECUTION qa: checker fresco | tier=checker model=gpt-5.5 effort=high" \
|
|
1433
|
+
&& { PASS=$((PASS+1)); echo " ok CLI humano emite una linea operable por fase"; } \
|
|
1434
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL execution-policy no imprime la linea de fase esperada"; }
|
|
1435
|
+
run dashboard --ledger L-ep.json --json 2>/dev/null | "$PY" -c "
|
|
1436
|
+
import json, sys
|
|
1437
|
+
d = json.load(sys.stdin)
|
|
1438
|
+
ph = {p['key']: p['execution'] for p in d['phases']}
|
|
1439
|
+
ok = ('execution_policy' in d
|
|
1440
|
+
and d['execution_policy']['source'] == 'config.defaults.execution_policy'
|
|
1441
|
+
and ph['qa']['model'] == 'gpt-5.5' and ph['qa']['effort'] == 'high'
|
|
1442
|
+
and ph['build']['method'] == 'implementar plan'
|
|
1443
|
+
and isinstance(d['readiness']['score'], (int, float)))
|
|
1444
|
+
sys.exit(0 if ok else 1)" \
|
|
1445
|
+
&& { PASS=$((PASS+1)); echo " ok dashboard expone execution_policy y anota phases sin ser score"; } \
|
|
1446
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL dashboard no expone execution_policy por fase"; }
|
|
1447
|
+
|
|
1448
|
+
echo "== T59 mirador-render (1.35.0): bird's-eye muestra policy model/effort =="
|
|
1449
|
+
"$PY" "$RENDER" --engine "$QL" --ledger L-ep.json --template "$TPL" --out ep-mir.html >/dev/null 2>&1
|
|
1450
|
+
"$PY" -c "
|
|
1451
|
+
import re, json, sys
|
|
1452
|
+
h = open('ep-mir.html', encoding='utf-8').read()
|
|
1453
|
+
m = re.search(r'const DATA = (\{.*\});\n/\*MIRADOR_DATA_END', h, re.S)
|
|
1454
|
+
d = json.loads(m.group(1))
|
|
1455
|
+
ok = ('id=\"exec\"' in h and 'Execution policy' in h
|
|
1456
|
+
and d['execution_policy']['phases']['qa']['model'] == 'gpt-5.5'
|
|
1457
|
+
and d['execution_policy']['phases']['qa']['effort'] == 'high')
|
|
1458
|
+
sys.exit(0 if ok else 1)" \
|
|
1459
|
+
&& { PASS=$((PASS+1)); echo " ok mirador renderiza el panel y preserva model/effort en DATA"; } \
|
|
1460
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL mirador no muestra execution_policy"; }
|
|
1461
|
+
|
|
1462
|
+
echo "== T60 discovery-intake (1.36.0): production finding reabre discovery =="
|
|
1463
|
+
printf -- "# ACCEPTANCE\n\n- [ ] AC-01 checkout total correcto\n" > acc-intake.md
|
|
1464
|
+
printf '{ "version": "1.36.0", "defaults": { "acceptance_file": "acc-intake.md" },\n "repos": [ {"name":"repo-c","path":"repo-c","type":"python"} ], "integration": {"enabled": false} }\n' > intake.json
|
|
1465
|
+
run init --config intake.json --out L-intake.json >/dev/null 2>&1
|
|
1466
|
+
run production-finding --ledger L-intake.json --repo repo-c --severity HIGH --source sentry --title "checkout total wrong" --evidence "Sentry INC-1" 2>/dev/null | grep -q "PF-001" \
|
|
1467
|
+
&& { PASS=$((PASS+1)); echo " ok production-finding crea PF-001 con evidencia de produccion"; } \
|
|
1468
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL production-finding no crea PF-001"; }
|
|
1469
|
+
run readiness --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1470
|
+
import json, sys
|
|
1471
|
+
d = json.load(sys.stdin)
|
|
1472
|
+
pf = d['discovery_intake']['production_findings']
|
|
1473
|
+
ok = (len(pf) == 1 and pf[0]['id'] == 'PF-001' and pf[0]['severity'] == 'HIGH'
|
|
1474
|
+
and d['facts']['production_findings_open'] == 1)
|
|
1475
|
+
sys.exit(0 if ok else 1)" \
|
|
1476
|
+
&& { PASS=$((PASS+1)); echo " ok readiness expone production findings como discovery_intake"; } \
|
|
1477
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL readiness no expone production findings"; }
|
|
1478
|
+
run readiness --ledger L-intake.json 2>/dev/null | grep -q "production findings open" \
|
|
1479
|
+
&& { PASS=$((PASS+1)); echo " ok readiness default avisa que discovery debe reabrirse"; } \
|
|
1480
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL readiness default no avisa production finding"; }
|
|
1481
|
+
run production-finding --ledger L-intake.json --id PF-001 --resolve --note "fed into SPEC next cycle" >/dev/null 2>&1
|
|
1482
|
+
run readiness --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1483
|
+
import json, sys
|
|
1484
|
+
d = json.load(sys.stdin)
|
|
1485
|
+
sys.exit(0 if d['discovery_intake']['production_findings'] == [] and d['facts']['production_findings_open'] == 0 else 1)" \
|
|
1486
|
+
&& { PASS=$((PASS+1)); echo " ok resolver PF-001 limpia el intake abierto"; } \
|
|
1487
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL resolver PF-001 no limpia discovery_intake"; }
|
|
1488
|
+
|
|
1489
|
+
echo "== T61 spec-doubt (1.36.0): SPEC-WRONG bloquea atajos y exige humano =="
|
|
1490
|
+
run spec-doubt --ledger L-intake.json --repo repo-c --kind spec-wrong --severity HIGH --note "AC dice sin impuesto, codigo real lo incluye" --evidence "demo con usuario" 2>/dev/null | grep -q "SD-001" \
|
|
1491
|
+
&& { PASS=$((PASS+1)); echo " ok spec-doubt crea SD-001 como duda de SPEC"; } \
|
|
1492
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL spec-doubt no crea SD-001"; }
|
|
1493
|
+
run readiness --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1494
|
+
import json, sys
|
|
1495
|
+
d = json.load(sys.stdin)
|
|
1496
|
+
sd = d['discovery_intake']['spec_doubts']
|
|
1497
|
+
ok = (len(sd) == 1 and sd[0]['id'] == 'SD-001' and sd[0]['kind'] == 'spec-wrong'
|
|
1498
|
+
and d['facts']['spec_doubts_open'] == 1)
|
|
1499
|
+
sys.exit(0 if ok else 1)" \
|
|
1500
|
+
&& { PASS=$((PASS+1)); echo " ok readiness expone spec-doubt como discovery_intake"; } \
|
|
1501
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL readiness no expone spec-doubt"; }
|
|
1502
|
+
run phase --ledger L-intake.json --repo repo-c --json 2>/dev/null | "$PY" -c "
|
|
1503
|
+
import json, sys
|
|
1504
|
+
d = json.load(sys.stdin)
|
|
1505
|
+
sys.exit(0 if d['phase'] == 'escalated' and any('spec-doubt' in e for e in d['evidence']) else 1)" \
|
|
1506
|
+
&& { PASS=$((PASS+1)); echo " ok phase deriva escalated si hay spec-doubt abierto"; } \
|
|
1507
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL phase ignora spec-doubt abierto"; }
|
|
1508
|
+
run spec-doubt --ledger L-intake.json --id SD-001 --resolve --decision "SPEC amended" --note "acceptance updated" >/dev/null 2>&1
|
|
1509
|
+
run readiness --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1510
|
+
import json, sys
|
|
1511
|
+
d = json.load(sys.stdin)
|
|
1512
|
+
sys.exit(0 if d['discovery_intake']['spec_doubts'] == [] and d['facts']['spec_doubts_open'] == 0 else 1)" \
|
|
1513
|
+
&& { PASS=$((PASS+1)); echo " ok resolver SD-001 limpia el intake abierto"; } \
|
|
1514
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL resolver SD-001 no limpia discovery_intake"; }
|
|
1515
|
+
|
|
1516
|
+
echo "== T62 ADR experiments (1.37.0): hipotesis visible, advisory, no score =="
|
|
1517
|
+
mkdir -p docs/adr
|
|
1518
|
+
cat > docs/adr/ADR-001-checkout-path.md <<'EOF'
|
|
1519
|
+
# ADR-001: Checkout path
|
|
1520
|
+
## Status: Experiment
|
|
1521
|
+
## Context
|
|
1522
|
+
Tenemos dos caminos viables y la respuesta depende de feedback real.
|
|
1523
|
+
## Decision
|
|
1524
|
+
Probar el nuevo checkout para aprender con bajo blast radius.
|
|
1525
|
+
## Hypothesis
|
|
1526
|
+
El checkout nuevo reduce abandonos sin subir errores.
|
|
1527
|
+
## Feedback Signal
|
|
1528
|
+
Conversion rate y errores de pago en produccion.
|
|
1529
|
+
## Review By: 2099-01-01
|
|
1530
|
+
## Promote Criteria
|
|
1531
|
+
Conversion estable o mejor y cero incidentes HIGH/BLOCKER.
|
|
1532
|
+
## Rollback / Supersede Criteria
|
|
1533
|
+
Suben errores de pago o aparece production-finding gateado.
|
|
1534
|
+
## Implementation Plan
|
|
1535
|
+
- Affected paths: checkout/*
|
|
1536
|
+
## Verification
|
|
1537
|
+
- [ ] Revisar senales de feedback.
|
|
1538
|
+
EOF
|
|
1539
|
+
cat > docs/adr/ADR-002-bad-experiment.md <<'EOF'
|
|
1540
|
+
# ADR-002: Bad experiment
|
|
1541
|
+
## Status: Experiment
|
|
1542
|
+
## Context
|
|
1543
|
+
Esto declara experimento pero no dice como se mide ni como se cierra.
|
|
1544
|
+
## Hypothesis
|
|
1545
|
+
Tal vez mejora.
|
|
1546
|
+
## Review By: 2000-01-01
|
|
1547
|
+
EOF
|
|
1548
|
+
run dashboard --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1549
|
+
import json, sys
|
|
1550
|
+
d = json.load(sys.stdin)
|
|
1551
|
+
adrs = {a['id']: a for a in d['adrs']}
|
|
1552
|
+
good = adrs['ADR-001']
|
|
1553
|
+
bad = adrs['ADR-002']
|
|
1554
|
+
summary = d['adr_experiments']
|
|
1555
|
+
ok = (
|
|
1556
|
+
good['status'] == 'prog'
|
|
1557
|
+
and good['adr_status'] == 'experiment'
|
|
1558
|
+
and good['experiment_valid'] is True
|
|
1559
|
+
and good['review_by'] == '2099-01-01'
|
|
1560
|
+
and good['expired'] is False
|
|
1561
|
+
and bad['experiment_valid'] is False
|
|
1562
|
+
and bad['expired'] is True
|
|
1563
|
+
and 'feedback_signal' in bad['experiment_missing']
|
|
1564
|
+
and summary['open'] == 2
|
|
1565
|
+
and summary['malformed'] == 1
|
|
1566
|
+
and summary['expired'] == 1
|
|
1567
|
+
and isinstance(d['readiness']['score'], (int, float))
|
|
1568
|
+
)
|
|
1569
|
+
sys.exit(0 if ok else 1)" \
|
|
1570
|
+
&& { PASS=$((PASS+1)); echo " ok dashboard expone experiment ADR valido/malformado como advisory"; } \
|
|
1571
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL dashboard no modela ADR experiment correctamente"; }
|
|
1572
|
+
"$PY" "$RENDER" --engine "$QL" --ledger L-intake.json --template "$TPL" --out exp-mir.html >/dev/null 2>&1
|
|
1573
|
+
"$PY" -c "
|
|
1574
|
+
import re, json, sys
|
|
1575
|
+
h = open('exp-mir.html', encoding='utf-8').read()
|
|
1576
|
+
m = re.search(r'const DATA = (\{.*\});\n/\*MIRADOR_DATA_END', h, re.S)
|
|
1577
|
+
d = json.loads(m.group(1))
|
|
1578
|
+
ok = ('experiment' in h and 'ADR-001' in h
|
|
1579
|
+
and d['adr_experiments']['open'] == 2
|
|
1580
|
+
and d['adrs'][0]['adr_status'] == 'experiment')
|
|
1581
|
+
sys.exit(0 if ok else 1)" \
|
|
1582
|
+
&& { PASS=$((PASS+1)); echo " ok mirador renderiza ADR experiment sin cambiar readiness"; } \
|
|
1583
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL mirador no muestra ADR experiment"; }
|
|
1584
|
+
|
|
1585
|
+
echo "== T63 spec-change-request (1.38.0): evidence -> human-signed contract change =="
|
|
1586
|
+
run spec-change-request --ledger L-intake.json --repo repo-c --source SD-001 --requested-change "AC-01 debe incluir impuesto" --evidence "demo + SD-001" --spec ACCEPTANCE.md 2>/dev/null | grep -q "SCR-001" \
|
|
1587
|
+
&& { PASS=$((PASS+1)); echo " ok spec-change-request crea SCR-001 desde evidencia"; } \
|
|
1588
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL spec-change-request no crea SCR-001"; }
|
|
1589
|
+
run readiness --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1590
|
+
import json, sys
|
|
1591
|
+
d = json.load(sys.stdin)
|
|
1592
|
+
scr = d['discovery_intake']['spec_change_requests']
|
|
1593
|
+
ok = (len(scr) == 1 and scr[0]['id'] == 'SCR-001' and scr[0]['source'] == 'SD-001'
|
|
1594
|
+
and d['facts']['spec_change_requests_open'] == 1)
|
|
1595
|
+
sys.exit(0 if ok else 1)" \
|
|
1596
|
+
&& { PASS=$((PASS+1)); echo " ok readiness expone SCR abierto como puente contractual"; } \
|
|
1597
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL readiness no expone SCR abierto"; }
|
|
1598
|
+
run phase --ledger L-intake.json --repo repo-c --json 2>/dev/null | "$PY" -c "
|
|
1599
|
+
import json, sys
|
|
1600
|
+
d = json.load(sys.stdin)
|
|
1601
|
+
sys.exit(0 if d['phase'] == 'escalated' and any('SCR-001' in e for e in d['evidence']) else 1)" \
|
|
1602
|
+
&& { PASS=$((PASS+1)); echo " ok phase deriva escalated si hay SCR humano pendiente"; } \
|
|
1603
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL phase ignora SCR abierto"; }
|
|
1604
|
+
run spec-change-request --ledger L-intake.json --id SCR-001 --resolve --decision accepted --note "ACCEPTANCE amended" --amended ACCEPTANCE.md >/dev/null 2>&1
|
|
1605
|
+
run readiness --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1606
|
+
import json, sys
|
|
1607
|
+
d = json.load(sys.stdin)
|
|
1608
|
+
sys.exit(0 if d['discovery_intake']['spec_change_requests'] == [] and d['facts']['spec_change_requests_open'] == 0 else 1)" \
|
|
1609
|
+
&& { PASS=$((PASS+1)); echo " ok resolver SCR-001 limpia el puente contractual abierto"; } \
|
|
1610
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL resolver SCR-001 no limpia discovery_intake"; }
|
|
1611
|
+
|
|
1612
|
+
echo "== T64 golden labels (1.38.0): intended vs observed-accidental visible =="
|
|
1613
|
+
mkdir -p gold
|
|
1614
|
+
printf 'legacy bug preserved\n' > gold/qr.received.txt
|
|
1615
|
+
cp gold/qr.received.txt gold/qr.approved.txt
|
|
1616
|
+
cat > golden-labels.json <<'EOF'
|
|
1617
|
+
{
|
|
1618
|
+
"fixtures": {
|
|
1619
|
+
"gold/qr.approved.txt": {
|
|
1620
|
+
"classification": "observed-accidental",
|
|
1621
|
+
"note": "legacy QR bug preserved for migration only"
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
EOF
|
|
1626
|
+
run golden-diff --dir gold --labels golden-labels.json --json 2>/dev/null | "$PY" -c "
|
|
1627
|
+
import json, sys
|
|
1628
|
+
d = json.load(sys.stdin)
|
|
1629
|
+
labels = d['golden_labels']
|
|
1630
|
+
ok = (d['verdict'] == 'CLEAN'
|
|
1631
|
+
and labels['observed_accidental'] == 1
|
|
1632
|
+
and labels['intended'] == 0
|
|
1633
|
+
and labels['unknown'] == 0
|
|
1634
|
+
and d['fixtures'][0]['classification'] == 'observed-accidental')
|
|
1635
|
+
sys.exit(0 if ok else 1)" \
|
|
1636
|
+
&& { PASS=$((PASS+1)); echo " ok golden-diff clasifica golden observado-accidental sin debilitar el byte compare"; } \
|
|
1637
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL golden-diff no expone labels intended/accidental"; }
|
|
1638
|
+
run golden-diff --dir gold --json 2>/dev/null | "$PY" -c "
|
|
1639
|
+
import json, sys
|
|
1640
|
+
d = json.load(sys.stdin)
|
|
1641
|
+
sys.exit(0 if d['golden_labels']['unknown'] == 1 else 1)" \
|
|
1642
|
+
&& { PASS=$((PASS+1)); echo " ok golden sin labels queda unknown, no inventa intencion"; } \
|
|
1643
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL golden-diff sin labels no queda unknown"; }
|
|
1644
|
+
|
|
1645
|
+
echo "== T65 calibration summary (1.38.0): post-merge facts calibran la retro =="
|
|
1646
|
+
run summary --ledger L-intake.json --json 2>/dev/null | "$PY" -c "
|
|
1647
|
+
import json, sys
|
|
1648
|
+
d = json.load(sys.stdin)
|
|
1649
|
+
cal = d['post_merge_calibration']
|
|
1650
|
+
ok = (cal['production_findings']['total'] == 1
|
|
1651
|
+
and cal['production_findings']['resolved'] == 1
|
|
1652
|
+
and cal['spec_doubts']['total'] == 1
|
|
1653
|
+
and cal['spec_doubts']['resolved'] == 1
|
|
1654
|
+
and cal['spec_change_requests']['total'] == 1
|
|
1655
|
+
and cal['spec_change_requests']['accepted'] == 1
|
|
1656
|
+
and cal['contract_reopen_signals'] == 3)
|
|
1657
|
+
sys.exit(0 if ok else 1)" \
|
|
1658
|
+
&& { PASS=$((PASS+1)); echo " ok summary expone calibracion post-merge desde PF/SD/SCR"; } \
|
|
1659
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL summary no expone calibracion post-merge"; }
|
|
1660
|
+
|
|
1661
|
+
echo "== T66 universal installer (1.40.1): Codex plugin + Claude adapter, dry-run safe =="
|
|
1662
|
+
INST_HOME="$SB/home-installer"
|
|
1663
|
+
mkdir -p "$INST_HOME"
|
|
1664
|
+
"$PY" "$KIT/install-uscha.py" version --json 2>/dev/null | "$PY" -c "
|
|
1665
|
+
import json, sys
|
|
1666
|
+
d = json.load(sys.stdin)
|
|
1667
|
+
ok = (d['source_version'] == '1.40.1' and 'codex' in d['targets'] and 'claude' in d['targets'])
|
|
1668
|
+
sys.exit(0 if ok else 1)" \
|
|
1669
|
+
&& { PASS=$((PASS+1)); echo " ok install-uscha version expone version fuente y targets"; } \
|
|
1670
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL install-uscha version no expone targets/version"; }
|
|
1671
|
+
"$PY" "$KIT/install-uscha.py" install --target both --home "$INST_HOME" --dry-run --json 2>/dev/null | "$PY" -c "
|
|
1672
|
+
import json, sys
|
|
1673
|
+
d = json.load(sys.stdin)
|
|
1674
|
+
ops = '\n'.join(o['path'].replace(chr(92), '/') for o in d['operations'])
|
|
1675
|
+
ok = (d['dry_run'] is True and 'plugins/uscha' in ops and '.agents/plugins/marketplace.json' in ops and '.claude/skills/uscha-devloop' in ops)
|
|
1676
|
+
sys.exit(0 if ok else 1)" \
|
|
1677
|
+
&& { PASS=$((PASS+1)); echo " ok dry-run planifica Codex plugin y Claude skills sin escribir"; } \
|
|
1678
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL dry-run no muestra plan universal"; }
|
|
1679
|
+
[ ! -e "$INST_HOME/.agents/plugins/uscha" ] && [ ! -e "$INST_HOME/.claude/skills/uscha-devloop" ] \
|
|
1680
|
+
&& { PASS=$((PASS+1)); echo " ok dry-run no crea instalacion"; } \
|
|
1681
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL dry-run escribio archivos"; }
|
|
1682
|
+
"$PY" "$KIT/install-uscha.py" install --target codex --home "$INST_HOME" --json >/dev/null 2>&1
|
|
1683
|
+
INST_HOME="$INST_HOME" "$PY" -c "
|
|
1684
|
+
import json, os, pathlib, sys
|
|
1685
|
+
h = pathlib.Path(os.environ['INST_HOME'])
|
|
1686
|
+
manifest = h/'plugins/uscha/.codex-plugin/plugin.json'
|
|
1687
|
+
market = h/'.agents/plugins/marketplace.json'
|
|
1688
|
+
engine = h/'plugins/uscha/skills/uscha-devloop/qa_ledger.py'
|
|
1689
|
+
marker = h/'plugins/uscha/uscha-install.json'
|
|
1690
|
+
ok = (manifest.exists() and market.exists() and engine.exists() and
|
|
1691
|
+
json.load(open(manifest, encoding='utf-8'))['version'] == '1.40.1' and
|
|
1692
|
+
json.load(open(marker, encoding='utf-8'))['target'] == 'codex')
|
|
1693
|
+
sys.exit(0 if ok else 1)" \
|
|
1694
|
+
&& { PASS=$((PASS+1)); echo " ok install codex crea plugin personal, marketplace y marker"; } \
|
|
1695
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL install codex incompleto"; }
|
|
1696
|
+
"$PY" "$KIT/install-uscha.py" doctor --target codex --home "$INST_HOME" --json 2>/dev/null | "$PY" -c "
|
|
1697
|
+
import json, sys
|
|
1698
|
+
d = json.load(sys.stdin)
|
|
1699
|
+
ok = (d['source_version'] == '1.40.1' and d['targets']['codex']['installed'] is True and d['targets']['codex']['version_match'] is True)
|
|
1700
|
+
sys.exit(0 if ok else 1)" \
|
|
1701
|
+
&& { PASS=$((PASS+1)); echo " ok doctor detecta Codex instalado y version match"; } \
|
|
1702
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL doctor no detecta install Codex"; }
|
|
1703
|
+
diff -qr "$KIT/.claude/skills" "$KIT/skills" -x __pycache__ >/dev/null 2>&1 \
|
|
1704
|
+
&& { PASS=$((PASS+1)); echo " ok Codex plugin skills mirror stays synced with canonical skills"; } \
|
|
1705
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL uscha-kit/skills drifted from .claude/skills"; }
|
|
1706
|
+
|
|
1707
|
+
|
|
1708
|
+
echo "== T67 npm router (1.40.1): npx package delegates to canonical installer =="
|
|
1709
|
+
if command -v node >/dev/null 2>&1; then
|
|
1710
|
+
node "$ROOT/bin/uscha.js" version --json 2>/dev/null | "$PY" -c "
|
|
1711
|
+
import json, sys
|
|
1712
|
+
d = json.load(sys.stdin)
|
|
1713
|
+
ok = (d['source_version'] == '1.40.1' and 'codex' in d['targets'] and 'claude' in d['targets'])
|
|
1714
|
+
sys.exit(0 if ok else 1)" \
|
|
1715
|
+
&& { PASS=$((PASS+1)); echo " ok npm router expone version/targets desde install-uscha.py"; } \
|
|
1716
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL npm router no delega correctamente al installer"; }
|
|
1717
|
+
else
|
|
1718
|
+
FAIL=$((FAIL+1)); echo " FAIL node no esta disponible para probar el router npm"
|
|
1719
|
+
fi
|
|
1720
|
+
if command -v npm >/dev/null 2>&1; then
|
|
1721
|
+
(cd "$ROOT" && npm_config_cache="$SB/npm-cache" npm pack --dry-run --json 2>/dev/null) | "$PY" -c "
|
|
1722
|
+
import json, sys
|
|
1723
|
+
d = json.load(sys.stdin)[0]
|
|
1724
|
+
files = {f['path'] for f in d['files']}
|
|
1725
|
+
ok = (d['name'] == '@andresmassello/uscha' and d['version'] == '1.40.1'
|
|
1726
|
+
and 'bin/uscha.js' in files and 'uscha-kit/install-uscha.py' in files
|
|
1727
|
+
and '.atl/skill-registry.md' not in files and 'handoff.md' not in files and 'mirador.html' not in files
|
|
1728
|
+
and not any('__pycache__' in f or f.endswith(('.pyc', '.pyo')) for f in files))
|
|
1729
|
+
sys.exit(0 if ok else 1)" \
|
|
1730
|
+
&& { PASS=$((PASS+1)); echo " ok npm pack dry-run incluye router/kit y excluye artefactos locales"; } \
|
|
1731
|
+
|| { FAIL=$((FAIL+1)); echo " FAIL npm pack dry-run no tiene el contenido esperado"; }
|
|
1732
|
+
else
|
|
1733
|
+
FAIL=$((FAIL+1)); echo " FAIL npm no esta disponible para probar package dry-run"
|
|
1734
|
+
fi
|
|
1735
|
+
|
|
1736
|
+
echo ""
|
|
1737
|
+
echo "RESULTADO: $PASS ok · $FAIL fail"
|
|
1738
|
+
cd / && rm -rf "$SB"
|
|
1739
|
+
[ "$FAIL" -eq 0 ]
|