crewforth 0.0.1 → 3.0.0-rc.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/CHANGELOG.md +3784 -0
- package/LICENSE +21 -0
- package/README.md +37 -7
- package/VERSION +1 -0
- package/adopt.sh +1670 -0
- package/bin/cli.js +497 -0
- package/kit/AGENT_TEMPLATE.md +92 -0
- package/kit/CLAUDE.md +228 -0
- package/kit/README.md +107 -0
- package/kit/agents/crew-backend-expert.md +95 -0
- package/kit/agents/crew-commit-agent.md +96 -0
- package/kit/agents/crew-database-expert.md +84 -0
- package/kit/agents/crew-devops-expert.md +82 -0
- package/kit/agents/crew-frontend-expert.md +85 -0
- package/kit/agents/crew-performance-expert.md +78 -0
- package/kit/agents/crew-planner.md +60 -0
- package/kit/agents/crew-privacy-agent.md +71 -0
- package/kit/agents/crew-review-agent.md +95 -0
- package/kit/agents/crew-security-expert.md +73 -0
- package/kit/agents/crew-session-manager.md +77 -0
- package/kit/agents/crew-test-expert.md +64 -0
- package/kit/eval/doctor.sh +465 -0
- package/kit/eval/gate-report.sh +137 -0
- package/kit/eval/golden-holdout.txt +53 -0
- package/kit/eval/golden-routing.txt +199 -0
- package/kit/eval/lib/crew-env.sh +14 -0
- package/kit/eval/lib/settings-json.awk +154 -0
- package/kit/eval/lib/star.sh +70 -0
- package/kit/eval/parser-conformance.sh +612 -0
- package/kit/eval/preflight.sh +186 -0
- package/kit/eval/routing-eval.sh +209 -0
- package/kit/eval/scan-skill.sh +162 -0
- package/kit/eval/smoke-test.sh +6693 -0
- package/kit/eval/update-guard.sh +81 -0
- package/kit/eval/utilization.sh +130 -0
- package/kit/hooks/board-sync.sh +106 -0
- package/kit/hooks/board.sh +962 -0
- package/kit/hooks/commit-msg +49 -0
- package/kit/hooks/context-usage.sh +353 -0
- package/kit/hooks/floor-blocklist.txt +124 -0
- package/kit/hooks/guard-bash.sh +1431 -0
- package/kit/hooks/guard-commit-scan.sh +511 -0
- package/kit/hooks/guard-write.sh +513 -0
- package/kit/hooks/pre-commit +577 -0
- package/kit/hooks/route-hint.sh +185 -0
- package/kit/hooks/secret-blocklist.txt +82 -0
- package/kit/hooks/session-guard.sh +149 -0
- package/kit/hooks/session-rehydrate.sh +41 -0
- package/kit/hooks/session-stats.sh +252 -0
- package/kit/hooks/session-update-check.sh +236 -0
- package/kit/hooks/skill-trust.sh +150 -0
- package/kit/hooks/trace-blocklist.txt +53 -0
- package/kit/settings.json +132 -0
- package/kit/skills/a11y/SKILL.md +44 -0
- package/kit/skills/adr/SKILL.md +74 -0
- package/kit/skills/api-design/SKILL.md +50 -0
- package/kit/skills/automode-policy/SKILL.md +91 -0
- package/kit/skills/automode-policy/references/policy.json +13 -0
- package/kit/skills/automode-policy/scripts/apply.sh +84 -0
- package/kit/skills/automode-policy/scripts/check.sh +102 -0
- package/kit/skills/backend-architecture/SKILL.md +103 -0
- package/kit/skills/brainstorm/SKILL.md +47 -0
- package/kit/skills/ci-pipeline/SKILL.md +38 -0
- package/kit/skills/commit-message/SKILL.md +82 -0
- package/kit/skills/commit-message/references/examples.md +8 -0
- package/kit/skills/confidence-check/SKILL.md +86 -0
- package/kit/skills/crew-board/SKILL.md +55 -0
- package/kit/skills/crew-brainstorm/SKILL.md +19 -0
- package/kit/skills/crew-code-review/SKILL.md +151 -0
- package/kit/skills/crew-code-review/references/history-search.md +19 -0
- package/kit/skills/crew-code-review/references/panel-mode.md +32 -0
- package/kit/skills/crew-code-review/references/receiving.md +23 -0
- package/kit/skills/crew-doctor/SKILL.md +36 -0
- package/kit/skills/crew-gates/SKILL.md +25 -0
- package/kit/skills/crew-handoff/SKILL.md +14 -0
- package/kit/skills/crew-plan/SKILL.md +18 -0
- package/kit/skills/crew-review/SKILL.md +23 -0
- package/kit/skills/crew-ship/SKILL.md +19 -0
- package/kit/skills/crew-skill/SKILL.md +61 -0
- package/kit/skills/crew-studio/SKILL.md +129 -0
- package/kit/skills/crew-update/SKILL.md +43 -0
- package/kit/skills/db-migration/SKILL.md +120 -0
- package/kit/skills/db-migration/references/backup-restore.md +33 -0
- package/kit/skills/db-migration/references/raw-sql.md +30 -0
- package/kit/skills/db-migration/references/tool-matrix.md +35 -0
- package/kit/skills/dependency-audit/SKILL.md +61 -0
- package/kit/skills/dependency-upgrade/SKILL.md +82 -0
- package/kit/skills/deploy/SKILL.md +100 -0
- package/kit/skills/deploy/references/proxy-ssl.md +56 -0
- package/kit/skills/deploy/references/rollback.md +18 -0
- package/kit/skills/deploy/references/scripts.md +9 -0
- package/kit/skills/deploy/references/topology-a.md +128 -0
- package/kit/skills/deploy/references/topology-b.md +44 -0
- package/kit/skills/docs-writer/SKILL.md +53 -0
- package/kit/skills/docs-writer/references/coauthoring.md +34 -0
- package/kit/skills/eval-grader/SKILL.md +75 -0
- package/kit/skills/eval-grader/references/method.md +51 -0
- package/kit/skills/frontend/SKILL.md +56 -0
- package/kit/skills/frontend/references/verify-contract.md +41 -0
- package/kit/skills/frontend-design/SKILL.md +71 -0
- package/kit/skills/frontend-design/references/design-review.md +34 -0
- package/kit/skills/frontend-rn-expo/SKILL.md +25 -0
- package/kit/skills/handoff/SKILL.md +71 -0
- package/kit/skills/handoff/references/cold-reader.md +51 -0
- package/kit/skills/i18n-integrity/SKILL.md +31 -0
- package/kit/skills/incident-runbook/SKILL.md +40 -0
- package/kit/skills/incident-runbook/references/postmortem.md +16 -0
- package/kit/skills/iterate/SKILL.md +40 -0
- package/kit/skills/mcp-builder/SKILL.md +54 -0
- package/kit/skills/mcp-builder/references/building.md +71 -0
- package/kit/skills/observability/SKILL.md +50 -0
- package/kit/skills/performance/SKILL.md +38 -0
- package/kit/skills/performance/references/bottlenecks.md +10 -0
- package/kit/skills/privacy-compliance/SKILL.md +75 -0
- package/kit/skills/red-team/SKILL.md +48 -0
- package/kit/skills/reflect/SKILL.md +57 -0
- package/kit/skills/release/SKILL.md +44 -0
- package/kit/skills/security-scan/SKILL.md +99 -0
- package/kit/skills/security-scan/references/ai-agents.md +107 -0
- package/kit/skills/security-scan/references/fronts.md +99 -0
- package/kit/skills/security-scan/references/prompting.md +23 -0
- package/kit/skills/security-scan/references/reporting.md +75 -0
- package/kit/skills/security-scan/references/verify.md +131 -0
- package/kit/skills/sonarqube-check/SKILL.md +108 -0
- package/kit/skills/sonarqube-check/references/local-engines.md +32 -0
- package/kit/skills/sonarqube-check/references/stand-up-server.md +25 -0
- package/kit/skills/spec-planning/SKILL.md +92 -0
- package/kit/skills/systematic-debugging/SKILL.md +57 -0
- package/kit/skills/systematic-debugging/references/techniques.md +44 -0
- package/kit/skills/teamboard/SKILL.md +88 -0
- package/kit/skills/teamboard/references/setup.md +37 -0
- package/kit/skills/testing/SKILL.md +49 -0
- package/kit/skills/testing/references/flaky-triage.md +51 -0
- package/kit/skills/threat-model/SKILL.md +57 -0
- package/kit/skills/threat-model/references/interview.md +51 -0
- package/kit/skills/threat-model/references/schema.md +42 -0
- package/kit/skills/token-budget/SKILL.md +51 -0
- package/kit/skills/trace-scan/SKILL.md +43 -0
- package/kit/skills/worktree/SKILL.md +40 -0
- package/kit/studio/README.md +266 -0
- package/kit/studio/ensure-node.sh +471 -0
- package/kit/studio/package.json +14 -0
- package/kit/studio/server/hooks/studio-gate.sh +169 -0
- package/kit/studio/server/index.js +902 -0
- package/kit/studio/server/lib/crew-env.js +11 -0
- package/kit/studio/server/lib/fleet.js +146 -0
- package/kit/studio/server/lib/graph.js +533 -0
- package/kit/studio/server/lib/instance.js +135 -0
- package/kit/studio/server/lib/kit-telemetry.js +182 -0
- package/kit/studio/server/lib/kit.js +158 -0
- package/kit/studio/server/lib/palette.js +118 -0
- package/kit/studio/server/lib/peers.js +72 -0
- package/kit/studio/server/lib/permissions.js +150 -0
- package/kit/studio/server/lib/projects.js +294 -0
- package/kit/studio/server/lib/roster.js +191 -0
- package/kit/studio/server/lib/session.js +309 -0
- package/kit/studio/server/lib/terminal.js +91 -0
- package/kit/studio/server/lib/transcript.js +100 -0
- package/kit/studio/web/app.js +1098 -0
- package/kit/studio/web/canvas.js +1179 -0
- package/kit/studio/web/chat.js +543 -0
- package/kit/studio/web/index.html +86 -0
- package/kit/studio/web/md.js +110 -0
- package/kit/studio/web/storage-migrate.js +23 -0
- package/kit/studio/web/style.css +1453 -0
- package/package.json +46 -8
- package/start.sh +748 -0
- package/bin/crewforth.js +0 -10
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PARSER CONFORMANCE — the gate's payload reader must agree with a real JSON parser.
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS FILE EXISTS. guard-bash.sh read its payload through a ladder: jq, then python3, then a pure-bash
|
|
5
|
+
# slice. Three parsers meant three behaviours, and every parser incident Crewforth has had came from the tiers
|
|
6
|
+
# DIVERGING rather than from any one of them being weak:
|
|
7
|
+
# * tier 3 was once `CMD="$INPUT"`, so every `git push` was hard-blocked as a force-push on any session id
|
|
8
|
+
# containing `-f8`. CI never saw it, because CI has jq.
|
|
9
|
+
# * Windows ships a python3 Store stub that satisfies `command -v` and cannot run, so stock desktops took
|
|
10
|
+
# tier 2 and FAILED OPEN.
|
|
11
|
+
# * a Windows-native jq writes stdout in TEXT mode, so tier 1 received `\` + CR + CR + LF where tier 3
|
|
12
|
+
# received JSON's two-character escapes, and an ordinary commit was refused on one platform only.
|
|
13
|
+
# Each was found by a single assertion going red long after the change that caused it.
|
|
14
|
+
#
|
|
15
|
+
# TWO MODES, AND THE FILE CHOOSES BETWEEN THEM BY LOOKING AT THE HOOK.
|
|
16
|
+
# LADDER PRESENT — the payload can still be read two ways, so the property is "both tiers reach the same
|
|
17
|
+
# VERDICT". Exit codes are compared, not bytes: the tiers legitimately hand the rules different bytes and a
|
|
18
|
+
# byte comparison would report those as defects and bury the one that is not.
|
|
19
|
+
# LADDER ABSENT — there is only one reader in production, so a tier comparison would be the slice against
|
|
20
|
+
# itself. The property becomes "the slice DECODES what a real parser decodes", measured against a reference
|
|
21
|
+
# that never goes through the hook at all, plus "the gate still reaches the verdict each row declares".
|
|
22
|
+
# The DECODE check runs in BOTH modes on purpose. It costs nothing while the ladder is there and it means the
|
|
23
|
+
# normalisations below are already pinned by the time the ladder goes, rather than being written the same day
|
|
24
|
+
# they become the only thing standing.
|
|
25
|
+
#
|
|
26
|
+
# THE MODE DETECTOR IS ITSELF CALIBRATED, IN BOTH DIRECTIONS. It is the one place where this whole file can go
|
|
27
|
+
# quiet: a detector that wrongly says "ladder absent" against a ladder-ful hook stops exercising tier selection
|
|
28
|
+
# and says nothing about it. So two fixtures are built and the detector must answer each correctly before any
|
|
29
|
+
# mode runs.
|
|
30
|
+
#
|
|
31
|
+
# THE DECODE RULES ARE TAKEN FROM THE HOOK'S CASE ARMS, NOT FROM A MEASUREMENT. Pinning whatever the slice
|
|
32
|
+
# happens to do today would make the pin agree with any future change. _json_unescape maps:
|
|
33
|
+
# \n -> LF \t -> TAB \r -> DROPPED \b and \f -> one space
|
|
34
|
+
# \uXXXX matching 00[2-7][0-9a-fA-F] (printable ASCII) -> that character
|
|
35
|
+
# any other \uXXXX -> a single `?` \<anything else> -> that character literally
|
|
36
|
+
# A row whose expected decode is `=` must match the reference EXACTLY; a row that exercises one of those
|
|
37
|
+
# normalisations carries the literal it must produce, so each normalisation is a stated fact and not an excuse.
|
|
38
|
+
#
|
|
39
|
+
# FIXTURES ARE BYTE-CHECKED BEFORE THEY ARE TRUSTED. A corpus of escapes is the easiest thing in this repo to
|
|
40
|
+
# measure vacuously: write `\\u002e` and the payload carries an escaped BACKSLASH followed by the letters
|
|
41
|
+
# u002e — inert text every reader agrees about while proving nothing; write it through a shell that expands
|
|
42
|
+
# escapes and the payload carries a real `.` and no parser ever sees an escape. Both shapes happened while this
|
|
43
|
+
# corpus was being built, on two different machines. So every escape row declares the escape it needs, the
|
|
44
|
+
# payload's bytes are searched for it, and a row whose fixture came out inert is reported UNMEASURED, never as
|
|
45
|
+
# a pass. Every backslash in a fixture is produced at RUN TIME by printf rather than written literally here,
|
|
46
|
+
# because a literal one has to survive every layer between an author and this file and in this repo it has not.
|
|
47
|
+
#
|
|
48
|
+
# Exit 0 = everything agreed. Exit 1 = a divergence, a broken fixture, or a calibration that did not answer.
|
|
49
|
+
# Exit 3 = nothing could be compared on this machine (no hook, or no reference parser). Never a pass.
|
|
50
|
+
|
|
51
|
+
set -u
|
|
52
|
+
LC_ALL=C
|
|
53
|
+
|
|
54
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
55
|
+
HOOK="$HERE/../hooks/guard-bash.sh"
|
|
56
|
+
[ -f "$HOOK" ] || HOOK="$HERE/../../kit/hooks/guard-bash.sh"
|
|
57
|
+
if [ ! -f "$HOOK" ]; then
|
|
58
|
+
# Not a divergence and not a broken fixture — there was nothing to measure at all, which is what rc 3 means
|
|
59
|
+
# everywhere else in this file. Exiting 1 here would report "the readers disagree" for a file that was merely
|
|
60
|
+
# copied somewhere else, and a caller that maps 1 to "gate failed" would chase a bug that does not exist.
|
|
61
|
+
echo "parser-conformance: guard-bash.sh bulunamadı ($HOOK) — HİÇBİR ŞEY ÖLÇÜLMEDİ, bu bir geçiş değildir"
|
|
62
|
+
exit 3
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
PASS=0; FAILED=0; UNMEASURED=0; KNOWN_OPEN=0
|
|
66
|
+
|
|
67
|
+
# AN ASSERTION THAT RAN IN A SUBSHELL IS INVISIBLE, and this file is as exposed to it as any other. `ok`/`bad`
|
|
68
|
+
# move shell VARIABLES, so inside `( … )` or a pipeline they move a copy in a child: the row prints, the total
|
|
69
|
+
# does not, and a `bad` there cannot fail the run. Colour cannot find it — only the DELTA can.
|
|
70
|
+
# So every assertion also appends the counter's value to a file. A file survives a subshell; a variable does
|
|
71
|
+
# not. Walking that log afterwards, each line must show the counter one higher than the line before, and any
|
|
72
|
+
# line that repeats means the assertion BEFORE it was lost. The append is a redirect, not a process.
|
|
73
|
+
# `$BASHPID != $$` is shorter and exact, and is not used: it does not exist on bash 3.2, which is what macOS
|
|
74
|
+
# runs, so it would detect nothing on one of the three supported platforms while looking like it worked.
|
|
75
|
+
# The label is stripped of line endings first — a decoded value can contain a real LF, which would split one
|
|
76
|
+
# log line into two and manufacture a repeat that is the harness's and not the suite's.
|
|
77
|
+
ASSERTLOG="$(mktemp)"
|
|
78
|
+
ok(){ PASS=$((PASS+1)); _al "OK $1"; printf ' \033[32mOK\033[0m %s\n' "$1"; }
|
|
79
|
+
bad(){ FAILED=$((FAILED+1)); _al "BAD $1"; printf ' \033[31mFAIL\033[0m %s\n' "$1"; }
|
|
80
|
+
unm(){ UNMEASURED=$((UNMEASURED+1)); _al "UNM $1"; printf ' \033[33m????\033[0m %s\n' "$1"; }
|
|
81
|
+
note(){ printf ' · %s\n' "$1"; }
|
|
82
|
+
_al(){ local l="${1//$'\n'/ }"; l="${l//$'\r'/ }"; printf '%s\t%s\n' "$((PASS+FAILED+UNMEASURED))" "$l" >> "$ASSERTLOG"; }
|
|
83
|
+
|
|
84
|
+
# THE RULE: the PARENT's logged values are exactly total, total-1, … 1, so walk the log BACKWARD expecting
|
|
85
|
+
# that chain. A line on the chain is a parent line; every line that is not was executed in a child.
|
|
86
|
+
#
|
|
87
|
+
# The first version used "a repeat at N means N-1 was lost", which is only true when each loss sits in its OWN
|
|
88
|
+
# subshell. Several assertions inside ONE `( … )` — the shape that actually occurred in this repo, five rows
|
|
89
|
+
# at once — make the counter ADVANCE inside the child (P+1, P+2, P+3) and then DROP when the parent resumes;
|
|
90
|
+
# that rule saw one drop and named one row, missing the rest. A synthetic fixture had modelled separate
|
|
91
|
+
# subshells, so it agreed with itself. Running the real shape is what showed the difference.
|
|
92
|
+
# The backward walk needs no special case for runs, for separate subshells, or for a loss at the very end.
|
|
93
|
+
_analyse(){ # $1 log, $2 visible total -> prints offenders in file order, then HITS=n
|
|
94
|
+
awk -F'\t' -v final="$2" '
|
|
95
|
+
{ n[NR]=$1; lab[NR]=$2 }
|
|
96
|
+
END { e=final+0; h=0
|
|
97
|
+
for (i=NR; i>=1; i--) { if (n[i]+0 == e) e--; else out[++h]=lab[i] }
|
|
98
|
+
for (j=h; j>=1; j--) printf " >> %s\n", out[j]
|
|
99
|
+
printf "HITS=%d\n", h }' "$1"; }
|
|
100
|
+
|
|
101
|
+
# Calibrated on a SYNTHETIC log before it is trusted, because a detector that quietly stopped working would
|
|
102
|
+
# report "0 findings" — a clean bill of health, and the same failure it exists to catch. The fixture carries
|
|
103
|
+
# the shape that broke the previous rule (a RUN inside one subshell) plus a loss at the very end, and a clean
|
|
104
|
+
# tail that must not be accused.
|
|
105
|
+
_cal="$(mktemp)"
|
|
106
|
+
printf '1\tp1\n2\tp2\n3\tRUN-1\n4\tRUN-2\n5\tRUN-3\n3\tp3\n4\tp4\n5\tSON\n' > "$_cal"
|
|
107
|
+
_co="$(_analyse "$_cal" 4)"
|
|
108
|
+
if [ "$(printf '%s' "$_co" | sed -n 's/^HITS=//p')" = 4 ] \
|
|
109
|
+
&& printf '%s' "$_co" | grep -q 'RUN-1' && printf '%s' "$_co" | grep -q 'RUN-2' \
|
|
110
|
+
&& printf '%s' "$_co" | grep -q 'RUN-3' && printf '%s' "$_co" | grep -q 'SON' \
|
|
111
|
+
&& ! printf '%s' "$_co" | grep -q 'p[1-4]'; then
|
|
112
|
+
_ALZ=1
|
|
113
|
+
else
|
|
114
|
+
_ALZ=0; printf ' \033[33m????\033[0m %s\n' "alt-kabuk dedektörü kendi kalibrasyonunu geçemedi — bu koşuda iddia kaybı ARANMAYACAK"
|
|
115
|
+
fi
|
|
116
|
+
rm -f "$_cal"
|
|
117
|
+
|
|
118
|
+
W="$(mktemp -d)"
|
|
119
|
+
# ASSERTLOG is created above, outside $W, so the trap names it too: every early exit (no reference parser,
|
|
120
|
+
# no extractable slice) used to leave one temp file behind per run.
|
|
121
|
+
trap 'cd /; rm -rf "$W" "$ASSERTLOG"' EXIT INT TERM
|
|
122
|
+
P="$W/payload.json"
|
|
123
|
+
BS="$(printf '\\')" # one real backslash, never written literally into this file
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
126
|
+
# THE MODE DETECTOR, and its calibration in both directions.
|
|
127
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
128
|
+
has_ladder(){ grep -q 'command -v jq' "$1"; }
|
|
129
|
+
|
|
130
|
+
mkdir -p "$W/det"
|
|
131
|
+
printf '%s\n' '#!/usr/bin/env bash' 'CMD=""' \
|
|
132
|
+
'if command -v jq >/dev/null 2>&1 && CMD="$(jq -r .x)"; then :; fi' > "$W/det/with.sh"
|
|
133
|
+
printf '%s\n' '#!/usr/bin/env bash' 'CMD=""' 'CMD="$(_json_slice "$INPUT" command)"' > "$W/det/without.sh"
|
|
134
|
+
|
|
135
|
+
echo "== mod seçici, iki yönde de kalibre ediliyor =="
|
|
136
|
+
DETOK=1
|
|
137
|
+
if has_ladder "$W/det/with.sh"; then ok "merdivenli bir kapıda seçici 'merdiven VAR' diyor"
|
|
138
|
+
else bad "seçici merdivenli kapıyı kaçırdı — kip seçimi güvenilmez"; DETOK=0; fi
|
|
139
|
+
if has_ladder "$W/det/without.sh"; then bad "seçici merdivensiz kapıda 'VAR' dedi — sessizleşmenin tersi, yine de yanlış"; DETOK=0
|
|
140
|
+
else ok "merdivensiz bir kapıda seçici 'merdiven YOK' diyor"; fi
|
|
141
|
+
[ "$DETOK" = 1 ] || { echo; echo "SEÇİCİ KALİBRASYONU BAŞARISIZ — hangi kipin koşacağı bilinemez, hiçbir şey raporlanmıyor"; exit 1; }
|
|
142
|
+
|
|
143
|
+
LADDER=0; has_ladder "$HOOK" && LADDER=1
|
|
144
|
+
note "kapıda okuma merdiveni: $( [ "$LADDER" = 1 ] && echo 'VAR — iki katman karşılaştırılacak' || echo 'YOK — tek okuyucu, referansa karşı çözüm karşılaştırılacak' )"
|
|
145
|
+
echo
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
148
|
+
# A REFERENCE PARSER. Real jq is preferred; otherwise a shim named `jq` is placed on PATH so the hook can still
|
|
149
|
+
# take its tier-1 branch while the ladder exists. The shim writes in binary: the Windows text-mode flavour is a
|
|
150
|
+
# separate, already-fixed defect and not what this file measures. perl comes before python3 because Git Bash
|
|
151
|
+
# ships perl with JSON::PP in core, while the python3 on PATH there is a Store stub that cannot run.
|
|
152
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
153
|
+
REFKIND=""
|
|
154
|
+
if command -v jq >/dev/null 2>&1 && printf '{"a":"b"}' | jq -r '.a' 2>/dev/null | grep -qx b; then REFKIND=jq
|
|
155
|
+
elif command -v perl >/dev/null 2>&1 && printf '{"a":"b"}' | perl -MJSON::PP -e 'local $/; print decode_json(<STDIN>)->{a}' 2>/dev/null | grep -qx b; then REFKIND=perl
|
|
156
|
+
elif command -v python3 >/dev/null 2>&1 && printf '{"a":"b"}' | python3 -c 'import sys,json;sys.stdout.write(json.load(sys.stdin)["a"])' 2>/dev/null | grep -qx b; then REFKIND=python3
|
|
157
|
+
elif command -v node >/dev/null 2>&1 && printf '{"a":"b"}' | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).a))' 2>/dev/null | grep -qx b; then REFKIND=node
|
|
158
|
+
fi
|
|
159
|
+
if [ -z "$REFKIND" ]; then
|
|
160
|
+
echo " bu makinede JSON çözebilen ikinci bir araç yok (jq, perl, python3, node)."
|
|
161
|
+
echo " HİÇBİR ŞEY KARŞILAŞTIRILMADI. Bu bir geçiş değildir."
|
|
162
|
+
exit 3
|
|
163
|
+
fi
|
|
164
|
+
note "referans çözücü: $REFKIND"
|
|
165
|
+
|
|
166
|
+
# THE REFERENCE'S TRANSPORT IS PART OF THE INSTRUMENT, and it was the one thing here taken on faith. Every
|
|
167
|
+
# fixture is byte-checked and the reader's decode is compared byte-exact, but what the reference HANDED BACK
|
|
168
|
+
# was never calibrated. A Windows-native jq opens stdout in TEXT mode and turns every LF it writes into CRLF,
|
|
169
|
+
# so the one row whose decoded value contains an LF diverges by a byte the READER never had — an instrument
|
|
170
|
+
# fault reported as a product divergence, in the file whose whole purpose is to refuse exactly that.
|
|
171
|
+
#
|
|
172
|
+
# The fix is not to normalise the difference away: a CR that a decode genuinely produced and a CR the pipe
|
|
173
|
+
# added look identical after normalisation, so stripping them would hide a real divergence to spare a fake one.
|
|
174
|
+
# Instead the reference is asked for BASE64, which is a single line with no embedded newline, so the platform's
|
|
175
|
+
# line-ending translation has nothing to act on and is out of the path for every reference equally. bash does
|
|
176
|
+
# the decoding, and both sides of the comparison are captured the same way so `$( )` treats them alike.
|
|
177
|
+
B64=0
|
|
178
|
+
if command -v base64 >/dev/null 2>&1 && [ "$(printf 'a\nb' | base64 | tr -d '\n' | base64 -d 2>/dev/null | od -An -tx1 | tr -d ' \n')" = "610a62" ]; then B64=1; fi
|
|
179
|
+
|
|
180
|
+
ref_encode(){ # the reference's answer for .tool_input.command, base64 on one line
|
|
181
|
+
case "$REFKIND" in
|
|
182
|
+
jq) jq -r '.tool_input.command // empty | @base64' < "$P" 2>/dev/null ;;
|
|
183
|
+
# Encode::encode first: JSON::PP hands back a CHARACTER string and encode_base64 wants octets, so an
|
|
184
|
+
# astral character made it return nothing at all. That failure was silent in the worst way — the
|
|
185
|
+
# known-divergence calibration compares the reference against the slice, and empty differs from the
|
|
186
|
+
# slice, so the row passed while the reference was carrying nothing.
|
|
187
|
+
perl) perl -MJSON::PP -MMIME::Base64 -MEncode -e 'local $/; my $j=decode_json(<STDIN>); print encode_base64(Encode::encode("UTF-8", ($j->{tool_input}{command} // "")), "");' < "$P" 2>/dev/null ;;
|
|
188
|
+
python3) python3 -c 'import sys,json,base64;sys.stdout.write(base64.b64encode(json.load(sys.stdin).get("tool_input",{}).get("command","").encode()).decode())' < "$P" 2>/dev/null ;;
|
|
189
|
+
node) node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let o;try{o=JSON.parse(s)}catch(e){process.exit(0)}process.stdout.write(Buffer.from(((o.tool_input||{}).command)||"").toString("base64"))})' < "$P" 2>/dev/null ;;
|
|
190
|
+
esac; }
|
|
191
|
+
|
|
192
|
+
ref_raw(){ # the direct read, kept as the fallback when nothing can decode base64
|
|
193
|
+
case "$REFKIND" in
|
|
194
|
+
jq) jq -r '.tool_input.command // empty' < "$P" 2>/dev/null ;;
|
|
195
|
+
perl) perl -MJSON::PP -e 'local $/; my $j=decode_json(<STDIN>); binmode(STDOUT); print(($j->{tool_input}{command} // ""));' < "$P" 2>/dev/null ;;
|
|
196
|
+
python3) python3 -c 'import sys,json;sys.stdout.write(json.load(sys.stdin).get("tool_input",{}).get("command",""))' < "$P" 2>/dev/null ;;
|
|
197
|
+
node) node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let o;try{o=JSON.parse(s)}catch(e){process.exit(0)}process.stdout.write(((o.tool_input||{}).command)||"")})' < "$P" 2>/dev/null ;;
|
|
198
|
+
esac; }
|
|
199
|
+
|
|
200
|
+
ref_decode(){ if [ "$B64" = 1 ]; then ref_encode | tr -d '\r\n' | base64 -d 2>/dev/null; else ref_raw; fi; }
|
|
201
|
+
|
|
202
|
+
SHIM="$W/shim"; mkdir -p "$SHIM"; T1PATH=""
|
|
203
|
+
if [ "$REFKIND" != jq ]; then
|
|
204
|
+
case "$REFKIND" in
|
|
205
|
+
perl) RUN='perl -MJSON::PP -e '\''local $/; my $j=decode_json(<STDIN>); binmode(STDOUT); my $k=$ENV{K}; print(($k eq "cmd" ? ($j->{tool_input}{command} // "") : ($j->{permission_mode} // "")), "\n");'\''' ;;
|
|
206
|
+
python3) RUN='python3 -c '\''import sys,os,json;d=json.load(sys.stdin);k=os.environ["K"];sys.stdout.write((d.get("tool_input",{}).get("command","") if k=="cmd" else d.get("permission_mode",""))+"\n")'\''' ;;
|
|
207
|
+
node) RUN='node -e '\''let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{let o;try{o=JSON.parse(s)}catch(e){process.exit(5)}const k=process.env.K;process.stdout.write((k==="cmd"?((o.tool_input||{}).command||""):(o.permission_mode||""))+"\n")})'\''' ;;
|
|
208
|
+
esac
|
|
209
|
+
{ echo '#!/usr/bin/env bash'
|
|
210
|
+
echo 'case "$2" in'
|
|
211
|
+
echo " '.tool_input.command // empty') K=cmd ;;"
|
|
212
|
+
echo " '.permission_mode // empty') K=pm ;;"
|
|
213
|
+
echo ' *) exit 3 ;;'
|
|
214
|
+
echo 'esac'
|
|
215
|
+
echo "export K; $RUN"; } > "$SHIM/jq"
|
|
216
|
+
chmod +x "$SHIM/jq"; T1PATH="$SHIM"
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
220
|
+
# THE SLICE, EXTRACTED. This is what production reads with once the ladder is gone, so it is compared against
|
|
221
|
+
# the reference directly rather than through the hook — a comparison routed through the hook would be the
|
|
222
|
+
# slice against itself the moment the ladder disappears.
|
|
223
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
224
|
+
{ echo '#!/usr/bin/env bash'; echo 'INPUT="$(cat)"'
|
|
225
|
+
awk '/^_json_slice\(\)/{f=1} f{print} f&&/^}$/{exit}' "$HOOK"
|
|
226
|
+
awk '/^_json_unescape\(\)/{f=1} f{print} f&&/^}$/{exit}' "$HOOK"
|
|
227
|
+
echo '_json_unescape "$(_json_slice "$INPUT" command)"'; } > "$W/dec.sh"
|
|
228
|
+
cp "$W/dec.sh" "$W/dec.good.sh"
|
|
229
|
+
if ! bash -n "$W/dec.sh" 2>/dev/null; then
|
|
230
|
+
echo " _json_slice / _json_unescape kapıdan çıkarılamadı — çözüm karşılaştırması yapılamaz"; exit 3; fi
|
|
231
|
+
slice_decode(){ bash "$W/dec.sh" < "$P" 2>/dev/null; }
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
234
|
+
# A REPO FOR THE GATE TO JUDGE. §4.6 reads a review record, so without one every commit blocks for a reason
|
|
235
|
+
# that has nothing to do with parsing and the corpus would compare two identical refusals.
|
|
236
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
237
|
+
R="$W/repo"; mkdir -p "$R"
|
|
238
|
+
( cd "$R" && git init -q . && git config user.email t@example.com && git config user.name t \
|
|
239
|
+
&& echo one > a.txt && git add a.txt && git commit -qm init && echo two >> a.txt && git add a.txt \
|
|
240
|
+
&& mkdir -p .claude ) >/dev/null 2>&1
|
|
241
|
+
( cd "$R" && printf '{"diff_oid":"%s","head":"%s","ts":"t"}\n' \
|
|
242
|
+
"$(git diff --cached | git hash-object --stdin)" "$(git rev-parse --verify --quiet HEAD)" \
|
|
243
|
+
> .claude/review-pass.json )
|
|
244
|
+
|
|
245
|
+
# THE PURE-BASH COLUMN HAS TO BE PURE-BASH ON EVERY MACHINE, not only on one that happens to lack jq. Run on
|
|
246
|
+
# the ambient PATH, `t3` is whatever rung the machine can reach — so on a jq box the "tier 3" column IS rung
|
|
247
|
+
# one, both columns mean the same thing, and a ladder-split row records `t3=0/t1=0` and reports that the
|
|
248
|
+
# divergence moved. That is the same defect as a row that only checks rc: the measurement's meaning depended on
|
|
249
|
+
# the environment rather than on what it claimed to measure. The interpreters are shadowed with stubs that
|
|
250
|
+
# EXIST and FAIL, which is the shape a stock Windows desktop already has (the Store python3), so the fallthrough
|
|
251
|
+
# being exercised is the real one and not "the binary is absent".
|
|
252
|
+
NOINT="$W/noint"; mkdir -p "$NOINT"
|
|
253
|
+
for _i in jq python3 python perl node; do
|
|
254
|
+
printf '%s\n' '#!/usr/bin/env bash' 'exit 1' > "$NOINT/$_i"; chmod +x "$NOINT/$_i"
|
|
255
|
+
done
|
|
256
|
+
t3(){ ( cd "$R" && PATH="$NOINT:$PATH" bash "$HOOK" < "$P" >/dev/null 2>&1; printf '%s' "$?" ); }
|
|
257
|
+
t1(){ if [ -n "$T1PATH" ]; then ( cd "$R" && PATH="$T1PATH:$PATH" bash "$HOOK" < "$P" >/dev/null 2>&1; printf '%s' "$?" )
|
|
258
|
+
else ( cd "$R" && bash "$HOOK" < "$P" >/dev/null 2>&1; printf '%s' "$?" ); fi; }
|
|
259
|
+
|
|
260
|
+
mk(){ printf '{"cwd":"/tmp","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"%s"},"tool_use_id":"x"}' "$1" > "$P"; }
|
|
261
|
+
mkraw(){ printf '%s' "$1" > "$P"; }
|
|
262
|
+
hexof(){ printf '%s' "$1" | od -An -tx1 | tr -d ' \n'; }
|
|
263
|
+
bytes(){ od -An -tx1 "$P" | tr -d ' \n'; }
|
|
264
|
+
|
|
265
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
266
|
+
# CALIBRATION. Nothing below is reported unless every question here answers.
|
|
267
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
268
|
+
echo
|
|
269
|
+
echo "-- kalibrasyon --"
|
|
270
|
+
CAL=1
|
|
271
|
+
mk 'rm -rf /'; a="$(t3)"
|
|
272
|
+
if [ "$a" = 2 ]; then ok "yıkıcı bir komut bloklanıyor (kapı gerçekten koşuyor)"; else bad "kalibrasyon: 'rm -rf /' rc=$a"; CAL=0; fi
|
|
273
|
+
mk 'ls -la'; a="$(t3)"
|
|
274
|
+
if [ "$a" = 0 ]; then ok "zararsız bir komut geçiyor (kapı aşırı bloklamıyor)"; else bad "kalibrasyon: 'ls -la' rc=$a"; CAL=0; fi
|
|
275
|
+
|
|
276
|
+
if [ "$LADDER" = 1 ]; then
|
|
277
|
+
# BOTH COLUMNS MUST MEAN WHAT THEY SAY, and each needs its own control because they fail in opposite ways.
|
|
278
|
+
#
|
|
279
|
+
# The rung-one column must really reach an interpreter: a stub that returns a DIFFERENT command for a
|
|
280
|
+
# harmless payload flips rc 0 -> 2 only if the branch is taken. Without this the second column can silently
|
|
281
|
+
# be the first column twice.
|
|
282
|
+
if [ -n "$T1PATH" ]; then
|
|
283
|
+
cp "$SHIM/jq" "$W/jq.real"
|
|
284
|
+
{ echo '#!/usr/bin/env bash'
|
|
285
|
+
echo "case \"\$2\" in '.tool_input.command // empty') printf '%s\\n' 'rm -rf /' ;; '.permission_mode // empty') printf '%s\\n' default ;; *) exit 3 ;; esac"
|
|
286
|
+
} > "$SHIM/jq"; chmod +x "$SHIM/jq"
|
|
287
|
+
mk 'ls -la'; c="$(t1)"
|
|
288
|
+
cp "$W/jq.real" "$SHIM/jq"; chmod +x "$SHIM/jq"
|
|
289
|
+
if [ "$c" = 2 ]; then ok "rung-1 sütunu gerçekten bir yorumlayıcıya ulaşıyor (stub başka komut döndürünce rc 0->2)"
|
|
290
|
+
else bad "kalibrasyon: rung-1 dalına girilmiyor — ikinci sütun birincinin kopyası (rc=$c)"; CAL=0; fi
|
|
291
|
+
else
|
|
292
|
+
mk 'ls -la'
|
|
293
|
+
if jq -r '.tool_input.command // empty' < "$P" 2>/dev/null | grep -qx 'ls -la'; then
|
|
294
|
+
ok "rung-1 sütunu gerçek jq ile koşuluyor"
|
|
295
|
+
else bad "kalibrasyon: gerçek jq beklendiği gibi cevap vermiyor"; CAL=0; fi
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# The MIRROR control, and it is the one this file did not have: the pure-bash column must IGNORE a working
|
|
299
|
+
# interpreter that is reachable. Run on the ambient PATH, `t3` was whatever rung the machine could reach — so
|
|
300
|
+
# on a jq box the "pure bash" column WAS rung one, both columns meant the same thing, and a ladder-split row
|
|
301
|
+
# recorded no divergence and reported that the divergence had moved. Same class as a row that only checks rc:
|
|
302
|
+
# the measurement's meaning depended on which binaries happened to be installed.
|
|
303
|
+
# A WORKING jq shim is placed BEHIND the shadow on the path t3 uses. If the shadow holds, `command -v jq`
|
|
304
|
+
# finds the failing stub first, the rung falls through and the slice answers `ls -la` for rc=0. If the shim is
|
|
305
|
+
# reached it returns `rm -rf /` and the row reads 2, which IS the shadow not taking.
|
|
306
|
+
DECOYBIN="$W/decoybin"; mkdir -p "$DECOYBIN"
|
|
307
|
+
{ echo '#!/usr/bin/env bash'
|
|
308
|
+
echo "case \"\$2\" in '.tool_input.command // empty') printf '%s\\n' 'rm -rf /' ;; '.permission_mode // empty') printf '%s\\n' default ;; *) exit 3 ;; esac"
|
|
309
|
+
} > "$DECOYBIN/jq"; chmod +x "$DECOYBIN/jq"
|
|
310
|
+
mk 'ls -la'
|
|
311
|
+
c="$( cd "$R" && PATH="$NOINT:$DECOYBIN:$PATH" bash "$HOOK" < "$P" >/dev/null 2>&1; printf '%s' "$?" )"
|
|
312
|
+
if [ "$c" = 0 ]; then ok "saf-bash sütunu erişilebilir bir yorumlayıcıyı GÖRMEZDEN geliyor (gölge tutuyor)"
|
|
313
|
+
else bad "kalibrasyon: gölge tutmuyor — saf-bash sütunu bir yorumlayıcıya ulaştı (rc=$c); bu sütun ortamın kurulu ikililerine bağlı demektir"; CAL=0; fi
|
|
314
|
+
else
|
|
315
|
+
# With one reader the shadow is a no-op, so asserting it would prove nothing. What is worth asserting instead
|
|
316
|
+
# is WHY it is a no-op: the hook names no interpreter on any line that also touches the payload. A rung that
|
|
317
|
+
# wrote $INPUT to a temp file and parsed the FILE would not name INPUT on the parsing line, so this looks for
|
|
318
|
+
# the interpreter and the payload variable independently and reports the pair rather than requiring them to
|
|
319
|
+
# share a line.
|
|
320
|
+
_INTERP="$(grep -nE '(^|[^A-Za-z_])(jq|python3?|perl|node|ruby)([^A-Za-z0-9_]|$)' "$HOOK" | grep -v '^[0-9]*: *#' | grep -vc '^$' || true)"
|
|
321
|
+
if [ "${_INTERP:-0}" = 0 ]; then ok "tek okuyucu: kapıda hiçbir yorumlayıcı adı geçmiyor (gölgeye gerek yok)"
|
|
322
|
+
else note "tek okuyucu: kapıda $_INTERP satırda yorumlayıcı adı geçiyor — payload'ı okumuyorlarsa sorun değil, ama yapısal kontrol onları adlandırmalı"; fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
# the decode comparison must be able to SEE a difference, and must be able to FAIL
|
|
326
|
+
mk "echo a${BS}ud83d${BS}ude00b"
|
|
327
|
+
d_s="$(slice_decode)"; d_r="$(ref_decode)"
|
|
328
|
+
# BOTH SIDES have to be checked, not just their inequality. An empty reference differs from the slice too, so
|
|
329
|
+
# "they differ" passed while the reference was carrying nothing at all — which is how a broken transport hid
|
|
330
|
+
# itself here for one run. The reference must produce the astral character; the slice must produce `??`.
|
|
331
|
+
if [ -z "$d_r" ]; then bad "kalibrasyon: referans vekil çift için BOŞ döndü — taşıma bozuk, 'farklılar' yanlış sebepten geçerdi"; CAL=0
|
|
332
|
+
elif [ "$d_s" = "$d_r" ]; then bad "kalibrasyon: bilinen fark görünmüyor — çözüm karşılaştırması boş olabilir"; CAL=0
|
|
333
|
+
elif [ "$d_s" = 'echo a??b' ]; then ok "bilinen çözüm farkı görünüyor (dilim '$d_s' · referans '$d_r')"
|
|
334
|
+
else bad "kalibrasyon: dilim vekil çift için 'echo a??b' vermedi ('$d_s') — belgelenen normalleştirme değişmiş"; CAL=0; fi
|
|
335
|
+
|
|
336
|
+
# MUTATION MUST-FAIL: break the extracted slice and require the comparison to notice
|
|
337
|
+
printf '%s\n' '#!/usr/bin/env bash' 'cat >/dev/null' "printf '%s' 'MUTASYON'" > "$W/dec.sh"
|
|
338
|
+
mk 'ls -la'; d_s="$(slice_decode)"; d_r="$(ref_decode)"
|
|
339
|
+
cp "$W/dec.good.sh" "$W/dec.sh"
|
|
340
|
+
if [ "$d_s" != "$d_r" ]; then ok "bozulmuş bir dilim çözüm karşılaştırmasında YAKALANIYOR (mutasyon testi)"
|
|
341
|
+
else bad "kalibrasyon: dilim kasten bozuldu ve karşılaştırma fark etmedi — kontrol ölü"; CAL=0; fi
|
|
342
|
+
mk 'ls -la'
|
|
343
|
+
if [ "$(slice_decode)" = "ls -la" ]; then ok "mutasyondan sonra gerçek dilim geri geldi"
|
|
344
|
+
else bad "kalibrasyon: mutasyon geri alınamadı, kalan satırlar bozuk dilimle ölçülürdü"; CAL=0; fi
|
|
345
|
+
|
|
346
|
+
# THE REFERENCE'S TRANSPORT, calibrated on values whose bytes are known before they are sent. This is the one
|
|
347
|
+
# the file did not have, and its absence produced a CI failure that named the reader for a fault in the
|
|
348
|
+
# measuring path. A reference that cannot carry a line ending intact cannot be used to judge one.
|
|
349
|
+
REF_LOSSY=0
|
|
350
|
+
mk "a${BS}nb"; _t="$(ref_decode | od -An -tx1 | tr -d ' \n')"
|
|
351
|
+
if [ "$_t" = "610a62" ]; then ok "referans gömülü bir LF'i bozmadan taşıyor ($( [ "$B64" = 1 ] && echo 'base64 taşıma' || echo 'doğrudan okuma' ))"
|
|
352
|
+
else REF_LOSSY=1; bad "referans TAŞIMASI BOZUK: LF için 610a62 beklenirken $_t döndü — bu ölçü aletinin kusuru, kapının değil"; fi
|
|
353
|
+
mk "a${BS}rb"; _t="$(ref_decode | od -An -tx1 | tr -d ' \n')"
|
|
354
|
+
if [ "$_t" = "610d62" ]; then ok "referans gömülü bir CR'i bozmadan taşıyor"
|
|
355
|
+
else REF_LOSSY=1; bad "referans TAŞIMASI BOZUK: CR için 610d62 beklenirken $_t döndü"; fi
|
|
356
|
+
# Deliberately NOT normalised away: a CR the decode really produced and a CR the pipe added are the same byte
|
|
357
|
+
# afterwards, so stripping them would hide a real divergence in order to spare a fake one. A lossy transport
|
|
358
|
+
# makes the CR/LF-bearing rows UNMEASURED instead, which is a result the next reader can act on.
|
|
359
|
+
[ "$REF_LOSSY" = 0 ] || note "taşıma kayıplı — satır sonu taşıyan satırlar ölçülemedi sayılacak, normalleştirilmeyecek"
|
|
360
|
+
|
|
361
|
+
if [ "$CAL" != 1 ]; then
|
|
362
|
+
echo; echo "KALİBRASYON BAŞARISIZ — korpus raporlanmıyor, çünkü sonucu okunamaz."; exit 1; fi
|
|
363
|
+
|
|
364
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
365
|
+
# THE CORPUS. Every row declares the verdict it expects, so a divergence is reported with its DIRECTION: a
|
|
366
|
+
# reader answering 0 where 2 was expected is a BLIND GATE; 2 where 0 was expected is an OVER-BLOCK, which
|
|
367
|
+
# teaches the user to reach for --no-verify and disarms all of §4.
|
|
368
|
+
# $3 `need` : the escape the fixture must really carry (`-` none, `hex:…` a whole byte sequence)
|
|
369
|
+
# $5 `dec` : `=` the slice must decode exactly what the reference decodes
|
|
370
|
+
# anything else: the literal the documented normalisation must produce
|
|
371
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
372
|
+
echo
|
|
373
|
+
echo "-- korpus --"
|
|
374
|
+
|
|
375
|
+
check_decode(){ # $1 label $2 expectation
|
|
376
|
+
local s r; s="$(slice_decode)"
|
|
377
|
+
# A lossy transport cannot judge a line ending, so those rows say so rather than being compared against a
|
|
378
|
+
# value the instrument mangled. The rows WITHOUT a line ending are unaffected and still run.
|
|
379
|
+
if [ "$REF_LOSSY" = 1 ]; then
|
|
380
|
+
case "$2$s" in
|
|
381
|
+
*$'\n'*|*$'\r'*) unm "$1 · satır sonu taşıyor ve referans taşıması kayıplı — ÖLÇÜLEMEDİ"; return ;;
|
|
382
|
+
esac
|
|
383
|
+
fi
|
|
384
|
+
if [ "$2" = "=" ]; then
|
|
385
|
+
r="$(ref_decode)"
|
|
386
|
+
if [ "$s" = "$r" ]; then ok "$1 · çözüm referansla aynı"
|
|
387
|
+
else bad "$1 · ÇÖZÜM AYRIŞIYOR: dilim [$s] · referans [$r]"; fi
|
|
388
|
+
else
|
|
389
|
+
if [ "$s" = "$2" ]; then ok "$1 · çözüm belgelenen normalleştirmeyi veriyor [$2]"
|
|
390
|
+
else bad "$1 · NORMALLEŞTİRME BOZULDU: beklenen [$2] · ölçülen [$s]"; fi
|
|
391
|
+
fi; }
|
|
392
|
+
|
|
393
|
+
run_row(){ # $1 label $2 expected rc $3 need $4 command $5 decode expectation
|
|
394
|
+
local label="$1" exp="$2" need="$3" cmd="$4" dec="$5" a b h nh dh
|
|
395
|
+
mk "$cmd"
|
|
396
|
+
if [ "$need" != "-" ]; then
|
|
397
|
+
h="$(bytes)"
|
|
398
|
+
case "$need" in
|
|
399
|
+
hex:*) nh="${need#hex:}"; dh="" ;;
|
|
400
|
+
*) nh="5c$(hexof "$need")"; dh="5c5c$(hexof "$need")" ;;
|
|
401
|
+
esac
|
|
402
|
+
if [ -n "$dh" ]; then case "$h" in *"$dh"*) unm "$label — fixture ATIL: kaçış çift yazılmış ($dh)"; return ;; esac; fi
|
|
403
|
+
case "$h" in *"$nh"*) ;; *) unm "$label — fixture ATIL: payload'da $nh baytları yok"; return ;; esac
|
|
404
|
+
fi
|
|
405
|
+
check_decode "$label" "$dec"
|
|
406
|
+
a="$(t3)"
|
|
407
|
+
if [ "$LADDER" = 1 ]; then
|
|
408
|
+
b="$(t1)"
|
|
409
|
+
if [ "$a" != "$b" ]; then
|
|
410
|
+
if [ "$a" = "$exp" ]; then bad "$label · AYRIŞIYOR: tier1 rc=$b, beklenen $exp $( [ "$exp" = 2 ] && echo '(tier1 KÖR)' || echo '(tier1 AŞIRI BLOKLUYOR)' )"
|
|
411
|
+
else bad "$label · AYRIŞIYOR: tier3 rc=$a, beklenen $exp $( [ "$exp" = 2 ] && echo '(tier3 KÖR)' || echo '(tier3 AŞIRI BLOKLUYOR)' )"; fi
|
|
412
|
+
return
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
if [ "$a" = "$exp" ]; then ok "$label · hüküm rc=$a"
|
|
416
|
+
else bad "$label · HÜKÜM YANLIŞ: rc=$a, beklenen $exp $( [ "$exp" = 2 ] && echo '(KÖR KAPI)' || echo '(AŞIRI BLOK)' )"; fi; }
|
|
417
|
+
|
|
418
|
+
U="${BS}u" # the two characters a JSON unicode escape starts with, built at run time
|
|
419
|
+
run_row 'düz commit' 2 - 'git commit -m c' '='
|
|
420
|
+
run_row 'pathspec commit' 2 - 'git commit -m c -- a.txt' '='
|
|
421
|
+
run_row 'devam + LF' 2 hex:5c5c5c6e "git commit ${BS}${BS}${BS}n -m c" '='
|
|
422
|
+
run_row 'devam + CRLF' 2 hex:5c5c5c72 "git commit ${BS}${BS}${BS}r${BS}n -m c" "git commit ${BS}"$'\n'" -m c"
|
|
423
|
+
run_row 'CRLF ayraç' 2 r "git commit -m c${BS}r${BS}necho done" "git commit -m c"$'\n'"echo done"
|
|
424
|
+
run_row 'yalnız CR' 2 r "git commit -m c${BS}recho done" 'git commit -m cecho done'
|
|
425
|
+
run_row 'sekme ayraçlı' 2 t "git commit${BS}t-m${BS}tc" "git commit"$'\t'"-m"$'\t'"c"
|
|
426
|
+
run_row 'kaçışlı tırnak' 2 '"' "git commit -m ${BS}\"a b${BS}\"" 'git commit -m "a b"'
|
|
427
|
+
run_row 'rm -rf kök' 2 - 'rm -rf /' '='
|
|
428
|
+
run_row 'force push' 2 - 'git push --force' '='
|
|
429
|
+
run_row 'git add -f' 2 - 'git add -f secrets.env' '='
|
|
430
|
+
run_row 'PowerShell özyinelemeli sil' 2 - "Remove-Item -Recurse -Force C:${BS}${BS}x" "Remove-Item -Recurse -Force C:${BS}x"
|
|
431
|
+
run_row 'gate betiği (düz)' 2 - 'echo x > .claude/hooks/guard-bash.sh' '='
|
|
432
|
+
run_row 'gate betiği (\u kaçışlı)' 2 u "echo x > ${U}002eclaude/hooks/guard-bash.sh" 'echo x > .claude/hooks/guard-bash.sh'
|
|
433
|
+
run_row 'gate yolunu okuma (\u)' 0 u "cat ${U}002eclaude/hooks/guard-bash.sh" 'cat .claude/hooks/guard-bash.sh'
|
|
434
|
+
run_row 'latin kaçışları -> ?' 0 u "echo ${U}00e7${U}011f${U}0131" 'echo ???'
|
|
435
|
+
run_row 'vekil çift -> ??' 0 u "echo a${U}d83d${U}de00b" 'echo a??b'
|
|
436
|
+
run_row 'NUL kaçışı -> ?' 0 u "echo a${U}0000b" 'echo a?b'
|
|
437
|
+
run_row 'backspace -> boşluk' 0 b "echo a${BS}bb" 'echo a b'
|
|
438
|
+
run_row 'formfeed -> boşluk' 0 f "echo a${BS}fc" 'echo a c'
|
|
439
|
+
run_row 'kaçışlı bölü' 0 / "cat a${BS}/b${BS}/c.txt" 'cat a/b/c.txt'
|
|
440
|
+
run_row 'zararsız komut' 0 - 'echo not-a-command' '='
|
|
441
|
+
run_row 'boş komut' 0 - '' '='
|
|
442
|
+
|
|
443
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
444
|
+
# ADVERSARIAL SHAPES — whole objects rather than commands, because the question is which VALUE each reader
|
|
445
|
+
# takes for a key, not what the command says. RFC 8259 leaves duplicate names undefined, so neither "first
|
|
446
|
+
# wins" nor "last wins" is wrong on its own; what matters is that the gate does not judge one value while
|
|
447
|
+
# showing the user another.
|
|
448
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
449
|
+
echo
|
|
450
|
+
echo "-- düşmanca biçimler --"
|
|
451
|
+
# AN EXIT CODE IS NOT A REASON. A row asserting rc=2 passes while the gate refuses for a rule that has nothing
|
|
452
|
+
# to do with the shape under test, and then keeps passing until the fixture changes. That happened here twice:
|
|
453
|
+
# * a payload whose only `"command"` sat in a SIBLING object was read as "the refusal closed the hole" — the
|
|
454
|
+
# slice had in fact read the sibling value and §4.5's `destructive rm -rf` fired on it. The harmless twin
|
|
455
|
+
# (`ls -la` in the same place) returns 0, which is what makes the real behaviour visible: the gate judges a
|
|
456
|
+
# string that is not the command that will run.
|
|
457
|
+
# * every adversarial payload carried `"cwd":"/tmp"`, where no review record exists, so each row whose
|
|
458
|
+
# command was a commit was refused by §4.6 before its own rule was ever reached.
|
|
459
|
+
# So: the payload's cwd is the prepared repo, each row may name the RULE it expects to fire, and the string the
|
|
460
|
+
# slice actually read is printed on every failure.
|
|
461
|
+
# EVERYTHING UP TO THE CLOSING PAREN, rather than a class of the characters a tag is expected to contain. A
|
|
462
|
+
# refusal protecting two sections is tagged `GUARD (§4.4/§4.5)`, and two narrower attempts both failed on it:
|
|
463
|
+
# `§[0-9.]+` stopped at the `/` and captured `GUARD (§4.4` with no closing paren, then `§[0-9./]+` stopped at
|
|
464
|
+
# the SECOND `§` and matched nothing at all, so the row reported "no rule fired" for a refusal that names two.
|
|
465
|
+
# Adding `§` to the class would also be fragile under `LC_ALL=C`, where it is two bytes. `[^)]*` has no opinion
|
|
466
|
+
# about what a tag may contain, which is the right amount of opinion for something whose job is to read one.
|
|
467
|
+
t3err(){ ( cd "$R" && PATH="$NOINT:$PATH" bash "$HOOK" < "$P" 2>&1 >/dev/null ) | tr -d '\r' | grep -oE 'GUARD \([^)]*\)' | head -1; }
|
|
468
|
+
slice_reads(){ bash "$W/dec.sh" < "$P" 2>/dev/null; }
|
|
469
|
+
|
|
470
|
+
adv(){ # $1 label $2 expected rc $3 rule $4 raw payload [$5 known-open:<t3>/<t1>]
|
|
471
|
+
# $3 = `-` do not check why it answered
|
|
472
|
+
# `§4.x` the hook's own tag must contain this
|
|
473
|
+
# An ambiguity refusal is named this way rather than by the destructive rule that used
|
|
474
|
+
# to fire: Crewforth refuses a payload it cannot read unambiguously instead of picking a
|
|
475
|
+
# winner, so the refusal comes FIRST and no command is judged at all. A row naming the
|
|
476
|
+
# destructive rule would be asserting a last-wins policy Crewforth does not have.
|
|
477
|
+
# `ladder-split` while a reader ladder exists this payload is EXPECTED to diverge, because the
|
|
478
|
+
# divergence IS the ladder's defect; with one reader the row must reach $2 instead.
|
|
479
|
+
local label="$1" exp="$2" want="$3" a b r known="${5:-}"
|
|
480
|
+
mkraw "${4//@CWD@/$R}"; a="$(t3)"
|
|
481
|
+
if [ "$LADDER" = 1 ]; then b="$(t1)"; else b="$a"; fi
|
|
482
|
+
if [ -n "$known" ] && [ "${CREW_CONFORMANCE_KNOWN_OPEN:-0}" = 1 ]; then
|
|
483
|
+
# A known-open row is a divergence this file FOUND that nothing has fixed yet. It is pinned to the exact
|
|
484
|
+
# shape observed so it cannot rot: if the shape moves, or if the row starts passing, this FAILS and says so.
|
|
485
|
+
local want="${known#known-open:}"
|
|
486
|
+
if [ "$a" = "$b" ] && [ "$a" = "$exp" ]; then bad "$label — ARTIK GEÇİYOR: bulgu düzeltilmiş, known-open işareti kaldırılmalı"; return; fi
|
|
487
|
+
if [ "$a/$b" = "$want" ]; then KNOWN_OPEN=$((KNOWN_OPEN+1))
|
|
488
|
+
printf ' \033[33mAÇIK\033[0m %s — bilinen ayrışma, kayıtlı: %s (beklenen %s)\n' "$label" "$a/$b" "$exp"; return; fi
|
|
489
|
+
bad "$label — bilinen ayrışma DEĞİŞTİ: kayıt $want, ölçülen $a/$b"; return
|
|
490
|
+
fi
|
|
491
|
+
if [ "$want" = "ladder-split" ]; then
|
|
492
|
+
# Two readers, one of which cannot see this shape at all — that IS the ladder's defect, so while the ladder
|
|
493
|
+
# exists the row asserts the divergence rather than pretending it is absent. Pinned to the exact pair, so a
|
|
494
|
+
# change in the shape fails, and so does the divergence DISAPPEARING while the ladder is still there: that
|
|
495
|
+
# would mean something else moved and the row is no longer describing what it says it describes.
|
|
496
|
+
if [ "$LADDER" = 1 ]; then
|
|
497
|
+
if [ "$a" = 2 ] && [ "$b" = 0 ]; then ok "$label · merdiven dururken AYRIŞIYOR (t3=2 t1=0) — merdivenin kusuru, silinince kapanmalı"
|
|
498
|
+
else bad "$label · merdivenli ayrışma DEĞİŞTİ: kayıt t3=2/t1=0, ölçülen t3=$a/t1=$b · dilim okudu [$(slice_reads)]"; fi
|
|
499
|
+
return
|
|
500
|
+
fi
|
|
501
|
+
if [ "$a" = "$exp" ]; then ok "$label · tek okuyucuda kapandı (rc=$a)"
|
|
502
|
+
else bad "$label · merdiven gitti ama HÂLÂ AÇIK: rc=$a, beklenen $exp · dilim okudu [$(slice_reads)]"; fi
|
|
503
|
+
return
|
|
504
|
+
fi
|
|
505
|
+
if [ "$LADDER" = 1 ] && [ "$a" != "$b" ]; then
|
|
506
|
+
bad "$label — AYRIŞIYOR: tier3 rc=$a · tier1 rc=$b (beklenen $exp) · dilim okudu [$(slice_reads)]"; return; fi
|
|
507
|
+
if [ "$a" != "$exp" ]; then
|
|
508
|
+
bad "$label — rc=$a, beklenen $exp $( [ "$exp" = 2 ] && echo '(KÖR KAPI)' || echo '(AŞIRI BLOK)' ) · dilim okudu [$(slice_reads)]"; return; fi
|
|
509
|
+
if [ "$want" != "-" ]; then
|
|
510
|
+
# the hook prints `GUARD (§4.5): …`, the row names `§4.5`, so this is a containment test and not equality —
|
|
511
|
+
# written as equality first, which reported "beklenen §4.5, ateşlenen GUARD (§4.5)" on rows that were right.
|
|
512
|
+
r="$(t3err)"
|
|
513
|
+
case "$r" in
|
|
514
|
+
*"$want"*) ok "$label (rc=$a, $want)"; return ;;
|
|
515
|
+
*) bad "$label — rc=$exp DOĞRU AMA SEBEP YANLIŞ: beklenen $want, ateşlenen ${r:-hiçbir kural} · dilim okudu [$(slice_reads)]"; return ;;
|
|
516
|
+
esac
|
|
517
|
+
fi
|
|
518
|
+
ok "$label (rc=$a)"; }
|
|
519
|
+
|
|
520
|
+
adv 'command anahtarı iki kez' 2 '§4.4' \
|
|
521
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"ls -la","command":"rm -rf /"},"tool_use_id":"x"}'
|
|
522
|
+
adv 'gövdede sahte command anahtarı' 2 '§4.5' \
|
|
523
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"description":"\"command\":\"ls\"","command":"rm -rf /"},"tool_use_id":"x"}'
|
|
524
|
+
adv 'gerçek anahtardan önce iç içe nesne' 2 '§4.4' \
|
|
525
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"meta":{"command":"ls -la"},"command":"rm -rf /"},"tool_use_id":"x"}'
|
|
526
|
+
adv 'kapanıştan önce kaçışlı tırnak' 2 '§4.5' \
|
|
527
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"rm -rf / --no-preserve-root \"x\""},"tool_use_id":"x"}'
|
|
528
|
+
adv 'kapanıştan önce kaçışlı ters bölü' 0 '-' \
|
|
529
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"echo C:\\\\"},"tool_use_id":"x"}'
|
|
530
|
+
adv 'anahtar sırası: tool_input önce' 2 '§4.5' \
|
|
531
|
+
'{"tool_input":{"command":"rm -rf /"},"tool_name":"Bash","cwd":"@CWD@","permission_mode":"default","tool_use_id":"x"}'
|
|
532
|
+
|
|
533
|
+
# THE SLICE MUST NOT READ A VALUE FROM OUTSIDE tool_input, and the pair below is what makes that visible. The
|
|
534
|
+
# dangerous shape alone proves nothing: it is refused, but by §4.5 firing on the string the slice pulled out of
|
|
535
|
+
# the SIBLING object — the gate judging text that is not the command that will run. The harmless twin is the
|
|
536
|
+
# row that shows it, because there is nothing in `ls -la` for any rule to catch.
|
|
537
|
+
# Reported once as "the refusal closes this hole". It does not. It was the twin that was missing.
|
|
538
|
+
adv 'kardeş nesnede yıkıcı komut' 2 'ladder-split' \
|
|
539
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"foo":1},"meta":{"command":"rm -rf /"},"tool_use_id":"x"}'
|
|
540
|
+
adv 'kardeş nesnede zararsız komut (İKİZ)' 0 '-' \
|
|
541
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"foo":1},"meta":{"command":"ls -la"},"tool_use_id":"x"}'
|
|
542
|
+
adv 'sade payload hâlâ geçiyor (ikiz)' 0 '-' \
|
|
543
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"ls -la"},"tool_use_id":"x"}'
|
|
544
|
+
|
|
545
|
+
# THE KEY NAME AS A VALUE. `_json_slice` searches for the bytes `"key"` and does not require the colon that
|
|
546
|
+
# follows a real key, so a payload carrying the key's NAME as a VALUE relocates the parse — and the ambiguity
|
|
547
|
+
# gates that count `"key":` do not see the same thing the parser found. Here the slice reads `ls` while the
|
|
548
|
+
# command that will actually run is `rm -rf /`.
|
|
549
|
+
adv 'anahtar adı DEĞER olarak (command)' 2 '§4.5' \
|
|
550
|
+
'{"cwd":"@CWD@","a":"command","ls":1,"permission_mode":"default","tool_name":"Bash","tool_input":{"command":"rm -rf /"},"tool_use_id":"x"}'
|
|
551
|
+
adv 'kontrol: aynı komut, gölgesiz (ikiz)' 2 '§4.5' \
|
|
552
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"rm -rf /"},"tool_use_id":"x"}'
|
|
553
|
+
|
|
554
|
+
# WHITESPACE BEFORE THE COLON is legal JSON and the parser and its guard must agree about it. The unspaced twin
|
|
555
|
+
# sits next to it so a red row can be read: if both are red the cause is the nested key, if only the spaced one
|
|
556
|
+
# is red the cause is the spacing.
|
|
557
|
+
adv 'iki noktadan önce boşluk' 2 '§4.5' \
|
|
558
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command" : "rm -rf /"},"tool_use_id":"x"}'
|
|
559
|
+
adv 'kontrol: boşluksuz ikiz' 2 '§4.5' \
|
|
560
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"rm -rf /"},"tool_use_id":"x"}'
|
|
561
|
+
|
|
562
|
+
# permission_mode: §4.4 fails CLOSED under bypassPermissions, so a key that shadows the real one moves the gate
|
|
563
|
+
# from refuse to ask, and under bypass the harness answers ask with allow. The cwd is the prepared repo, not
|
|
564
|
+
# /tmp: with no review record §4.6 refuses a commit before §4.4 is ever reached, and every one of these rows
|
|
565
|
+
# would have passed on that unrelated rule. That is exactly what they did before this was fixed.
|
|
566
|
+
adv 'permission_mode iki kez (gölgeli bypass)' 2 '§4.4' \
|
|
567
|
+
'{"cwd":"@CWD@","meta":{"permission_mode":"default"},"permission_mode":"bypassPermissions","tool_name":"Bash","tool_input":{"command":"git commit -m x"},"tool_use_id":"x"}'
|
|
568
|
+
adv 'permission_mode DEĞER olarak' 2 '§4.4' \
|
|
569
|
+
'{"cwd":"@CWD@","a":"permission_mode","default":1,"permission_mode":"bypassPermissions","tool_name":"Bash","tool_input":{"command":"git commit -m x"},"tool_use_id":"x"}'
|
|
570
|
+
adv 'dürüst bypass (ikiz)' 2 '§4.4' \
|
|
571
|
+
'{"cwd":"@CWD@","permission_mode":"bypassPermissions","tool_name":"Bash","tool_input":{"command":"git commit -m x"},"tool_use_id":"x"}'
|
|
572
|
+
adv 'permission_mode yok (ikiz, meşru)' 2 '§4.4' \
|
|
573
|
+
'{"cwd":"@CWD@","tool_name":"Bash","tool_input":{"command":"git commit -m x"},"tool_use_id":"x"}'
|
|
574
|
+
adv 'dürüst default: ask, blok değil (ikiz)' 0 '-' \
|
|
575
|
+
'{"cwd":"@CWD@","permission_mode":"default","tool_name":"Bash","tool_input":{"command":"git commit -m x"},"tool_use_id":"x"}'
|
|
576
|
+
|
|
577
|
+
# The key NAME written as a unicode escape. A reader that decodes key names finds `command` and judges
|
|
578
|
+
# `rm -rf /`; one that searches for the literal bytes finds nothing and the gate has no command to judge. The
|
|
579
|
+
# safe answer is the refusal either way, so 2 is expected and a reader answering 0 is BLIND, not lenient.
|
|
580
|
+
# The backslash is built at run time and the payload's BYTES are checked before the row is allowed to count:
|
|
581
|
+
# written literally, the escape has arrived here as an escaped backslash — inert text every reader agrees
|
|
582
|
+
# about — and the row would have reported a pass while measuring nothing.
|
|
583
|
+
ESCKEY="{\"cwd\":\"@CWD@\",\"permission_mode\":\"default\",\"tool_name\":\"Bash\",\"tool_input\":{\"${BS}u0063ommand\":\"rm -rf /\"},\"tool_use_id\":\"x\"}"
|
|
584
|
+
case "$(printf '%s' "$ESCKEY" | od -An -tx1 | tr -d ' \n')" in
|
|
585
|
+
*5c5c7530303633*) bad 'anahtar adı \u kaçışlı — fixture ATIL: çift ters bölü' ;;
|
|
586
|
+
*5c7530303633*) adv 'anahtar adı \u kaçışlı' 2 '-' "$ESCKEY" ;;
|
|
587
|
+
*) bad 'anahtar adı \u kaçışlı — fixture kurulamadı' ;;
|
|
588
|
+
esac
|
|
589
|
+
|
|
590
|
+
echo
|
|
591
|
+
# Every assertion must have been visible to the parent. A row lost to a subshell is worse than a red one: a
|
|
592
|
+
# `bad` there prints and cannot fail the run, so the gate is silently always-green from that line onward.
|
|
593
|
+
if [ "$_ALZ" = 1 ]; then
|
|
594
|
+
_ao="$(_analyse "$ASSERTLOG" "$((PASS+FAILED+UNMEASURED))")"
|
|
595
|
+
_ah="$(printf '%s' "$_ao" | sed -n 's/^HITS=//p')"
|
|
596
|
+
if [ "${_ah:-0}" != 0 ]; then
|
|
597
|
+
printf ' \033[31mFAIL\033[0m %s\n' "$_ah iddia ALT KABUKTA koştu — sayaca ulaşmadılar, bir 'bad' orada görünmez olurdu:"
|
|
598
|
+
printf '%s\n' "$_ao" | grep '>>'
|
|
599
|
+
FAILED=$((FAILED+1))
|
|
600
|
+
fi
|
|
601
|
+
fi
|
|
602
|
+
rm -f "$ASSERTLOG"
|
|
603
|
+
printf 'PARSER-CONFORMANCE: %s geçti · %s başarısız · %s ölçülemedi · %s bilinen-açık (kip: %s · referans: %s)\n' \
|
|
604
|
+
"$PASS" "$FAILED" "$UNMEASURED" "$KNOWN_OPEN" \
|
|
605
|
+
"$( [ "$LADDER" = 1 ] && echo 'merdivenli' || echo 'tek okuyucu' )" "$REFKIND"
|
|
606
|
+
if [ "$KNOWN_OPEN" -gt 0 ]; then
|
|
607
|
+
echo "$KNOWN_OPEN bilinen ayrışma AÇIK ve bu koşuda başarısızlık sayılmadı (CREW_CONFORMANCE_KNOWN_OPEN=1)."
|
|
608
|
+
fi
|
|
609
|
+
if [ "$FAILED" -gt 0 ]; then echo "Okuyucu referanstan ayrıştı ya da bir hüküm yanlış. Bu kapatılmalı."; exit 1; fi
|
|
610
|
+
if [ "$UNMEASURED" -gt 0 ]; then echo "Bazı satırlar ölçülemedi — bu bir geçiş değildir, fixture'ları düzeltin."; exit 1; fi
|
|
611
|
+
echo "Kapının okuyucusu her vakada referansla aynı sonuca vardı."
|
|
612
|
+
exit 0
|