@ccoalm/ccl-skills 0.9.0 → 0.11.0
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/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +4 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +23 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +178 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +127 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +2 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +11 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +8 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dispatch-owner-skills.md +9 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/problem-resolution-and-learning.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/tag-and-prod-pipeline-gate.md +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +17 -20
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attention-budget-ratchet.md +37 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +26 -44
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +24 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +5 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +59 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +35 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-contract-anchors.sh +126 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +197 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/contract-anchors.tsv +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +210 -36
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +3 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/gate_receipt.py +576 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +35 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +476 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_antipattern_grep_panel.sh +80 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_body_compliance_grading.sh +99 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +81 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +28 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +251 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_contract_anchors.sh +196 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +222 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +16 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_frozen_case_sanctity.sh +178 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_frozen_case_sanctity_selfproof.sh +108 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_gate_receipt.sh +431 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_pinned_phrase_mutation_walk.sh +151 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +336 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_bank_integrity.sh +86 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +41 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +141 -28
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +139 -38
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +9 -8
- package/dist/assets/release.json +110 -45
- package/package.json +1 -1
|
@@ -194,6 +194,86 @@ run_gate
|
|
|
194
194
|
assert_not_contains "impact_chain_firing_path_missing" "$out" "a changed shebang owner executable should satisfy the firing-path gate"
|
|
195
195
|
assert_rc "$rc" 0 "a complete RED row with a changed owner executable must pass"
|
|
196
196
|
|
|
197
|
+
# Non-ASCII anchors must be accepted, and the proof must be DIFFERENTIAL rather
|
|
198
|
+
# than one literal passing: a special-cased implementation, or one that handles
|
|
199
|
+
# Han but not a combining mark, would satisfy a single-literal leg. Each row below
|
|
200
|
+
# pairs a non-ASCII anchor with an ASCII anchor matched on everything the gate
|
|
201
|
+
# actually predicates on - uniqueness, the 16-character bar, list-line shape, a
|
|
202
|
+
# whitelisted normative verb - so the two verdicts must agree. This exists because
|
|
203
|
+
# a round concluded from reading the wrong lambda (`raw_blob_at` forces binary;
|
|
204
|
+
# `locator_valid` resolves through `blob_at`, which does not) that a non-ASCII
|
|
205
|
+
# anchor could never validate, and recorded a defect that was not there.
|
|
206
|
+
#
|
|
207
|
+
# Representations covered: Han; a precomposed Latin letter (NFC, two bytes); the
|
|
208
|
+
# same letter decomposed into base + combining mark (NFD, so the anchor carries a
|
|
209
|
+
# codepoint that is not a character on its own).
|
|
210
|
+
anchor_pair_case() {
|
|
211
|
+
# $1 = case label, $2 = non-ASCII anchor, $3 = ASCII anchor of the same shape
|
|
212
|
+
local label="$1" nonascii="$2" ascii="$3" verdict_nonascii verdict_ascii
|
|
213
|
+
new_case "case-ref-anchor-pair-$label-nonascii"
|
|
214
|
+
printf '\n- %s\n' "$nonascii" >> "$REPO/$UPSTREAM_REF"
|
|
215
|
+
printf '| Fixture anchor pair %s non-ASCII | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#%s | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' "$label" "$nonascii" >> "$REGISTER"
|
|
216
|
+
routing_surface_downscope
|
|
217
|
+
commit_case "anchor pair $label non-ASCII"
|
|
218
|
+
run_gate
|
|
219
|
+
verdict_nonascii="$rc"
|
|
220
|
+
|
|
221
|
+
new_case "case-ref-anchor-pair-$label-ascii"
|
|
222
|
+
printf '\n- %s\n' "$ascii" >> "$REPO/$UPSTREAM_REF"
|
|
223
|
+
printf '| Fixture anchor pair %s ASCII | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#%s | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' "$label" "$ascii" >> "$REGISTER"
|
|
224
|
+
routing_surface_downscope
|
|
225
|
+
commit_case "anchor pair $label ASCII"
|
|
226
|
+
run_gate
|
|
227
|
+
verdict_ascii="$rc"
|
|
228
|
+
|
|
229
|
+
assert_rc "$verdict_ascii" 0 "the ASCII control of pair $label must be accepted"
|
|
230
|
+
assert_rc "$verdict_nonascii" "$verdict_ascii" "pair $label: the non-ASCII anchor must get the same verdict as its ASCII control"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
anchor_pair_case han \
|
|
234
|
+
'下游执行者必须在收到该契约变更时同步更新其验证面。' \
|
|
235
|
+
'The downstream executor must refresh its verification surface on this change.'
|
|
236
|
+
# Same sentence and the same whitelisted verb in both arms; only the accented
|
|
237
|
+
# codepoint differs, so a verdict difference can only come from byte composition.
|
|
238
|
+
anchor_pair_case nfc \
|
|
239
|
+
'The owner must refresh its vérification surface on this change.' \
|
|
240
|
+
'The owner must refresh its verification surface on this change.'
|
|
241
|
+
anchor_pair_case nfd \
|
|
242
|
+
'The owner must refresh its vérification surface on that change.' \
|
|
243
|
+
'The owner must refresh its verification surface on that change.'
|
|
244
|
+
|
|
245
|
+
# When one owner carries several RED rows, the owner-level check reddens the whole
|
|
246
|
+
# package for a single bad row — so the diagnostic must name WHICH row failed.
|
|
247
|
+
# Without it the author inspects the row they just wrote (usually the correct one)
|
|
248
|
+
# while an unrelated row is the offender; that cost one round three iterations.
|
|
249
|
+
new_case case-ref-firing-path-names-offending-row
|
|
250
|
+
printf '\n- 下游执行者必须在收到该契约变更时同步更新其验证面。\n' >> "$REPO/$UPSTREAM_REF"
|
|
251
|
+
printf '| Fixture good row beside a bad one | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#下游执行者必须在收到该契约变更时同步更新其验证面 | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' >> "$REGISTER"
|
|
252
|
+
printf '| Fixture offending row with an unresolvable anchor | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#this anchor text was never added by this round | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' >> "$REGISTER"
|
|
253
|
+
routing_surface_downscope
|
|
254
|
+
commit_case "one good row beside one unresolvable row"
|
|
255
|
+
run_gate
|
|
256
|
+
assert_rc "$rc" 1 "an unresolvable anchor on any bound RED row must fail the owner"
|
|
257
|
+
assert_contains "impact_chain_firing_path_missing" "$out" "checker should report the firing-path verdict"
|
|
258
|
+
assert_contains "Fixture offending row with an unresolvable anchor" "$out" "the diagnostic must name the offending row"
|
|
259
|
+
assert_not_contains "offending row: Fixture good row beside a bad one" "$out" "the diagnostic must not name a row whose anchor resolves"
|
|
260
|
+
|
|
261
|
+
# The offending-row line renders repository text a contributor controls onto a
|
|
262
|
+
# terminal/CI channel. Control and invisible characters must be escaped, not merely
|
|
263
|
+
# truncated: raw ANSI or carriage-return bytes can erase or restyle surrounding
|
|
264
|
+
# output, and bidi overrides can reorder it, so a reader sees a diagnostic that does
|
|
265
|
+
# not match what the gate decided. Both reviewer lanes raised this independently.
|
|
266
|
+
new_case case-ref-offending-row-escapes-control-bytes
|
|
267
|
+
printf '\n- 下游执行者必须在收到该契约变更时同步更新其验证面。\n' >> "$REPO/$UPSTREAM_REF"
|
|
268
|
+
printf '| Fixture control-byte row \033[31m\rSPOOFED\u202E | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#this anchor text was never added by this round | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' >> "$REGISTER"
|
|
269
|
+
routing_surface_downscope
|
|
270
|
+
commit_case "offending row carrying control bytes"
|
|
271
|
+
run_gate
|
|
272
|
+
assert_rc "$rc" 1 "an unresolvable anchor must still fail with a control-byte row"
|
|
273
|
+
assert_contains "\\u001B" "$out" "the diagnostic must escape an ANSI introducer rather than emit it"
|
|
274
|
+
assert_contains "\\u000D" "$out" "the diagnostic must escape a carriage return rather than emit it"
|
|
275
|
+
assert_contains "\\u202E" "$out" "the diagnostic must escape a bidi override rather than emit it"
|
|
276
|
+
|
|
197
277
|
# A malformed impact-chain-looking row (wrong cell count) is warned but stays
|
|
198
278
|
# advisory: hard-blocking would false-positive on the register's many other
|
|
199
279
|
# legitimate table shapes, and deliberate mangling is outside the trust model
|
|
@@ -1329,6 +1409,6 @@ assert_rc "$rc" 1 "a directory masquerading as SKILL.md must not read as a prese
|
|
|
1329
1409
|
assert_contains "platform-observability/SKILL.md" "$out" "the masqueraded owner must be named"
|
|
1330
1410
|
|
|
1331
1411
|
assert_rc "$full_check_runs" 1 "fixture suite must retain exactly one full checker wiring case"
|
|
1332
|
-
assert_rc "$gate_runs"
|
|
1412
|
+
assert_rc "$gate_runs" 88 "all remaining impact-chain fixtures must run the standalone gate"
|
|
1333
1413
|
|
|
1334
1414
|
echo "test_check_ccl_impact_chain_refscripts: ok"
|
|
@@ -114,6 +114,12 @@ fast_tests=(
|
|
|
114
114
|
test_shared_git_surface_gate.sh
|
|
115
115
|
test_extraction_review_gate.sh
|
|
116
116
|
test_validate_extraction_review_state.sh
|
|
117
|
+
# Merge-side binding between the landing candidate and the review evidence that
|
|
118
|
+
# inspected it: own throwaway git repo, no clone, seconds.
|
|
119
|
+
test_review_ledger_binding.sh
|
|
120
|
+
# Candidate-SHA-bound gate receipts (mint/verify): own throwaway git repo,
|
|
121
|
+
# no clone, seconds — belongs in the lane every run exercises.
|
|
122
|
+
test_gate_receipt.sh
|
|
117
123
|
test_check_ccl_route_drift.sh
|
|
118
124
|
test_check_sync_pointers.sh
|
|
119
125
|
test_check_ccl_register_pending_exclusion.sh
|
|
@@ -137,6 +143,13 @@ fast_tests=(
|
|
|
137
143
|
test_validate_skill_cross_refs.sh
|
|
138
144
|
test_git_identity_predicate_gate.sh
|
|
139
145
|
test_liveness_predicate_gate.sh
|
|
146
|
+
# 074: contract-anchor gate self-proof (tmp fixtures + one real-repo leg,
|
|
147
|
+
# seconds) and the anti-patterns panel structural check (awk over one file).
|
|
148
|
+
test_check_contract_anchors.sh
|
|
149
|
+
test_antipattern_grep_panel.sh
|
|
150
|
+
# 075: body-compliance grading contract (grade() walk, fail-closed --ids legs,
|
|
151
|
+
# stub-model denominator/forbidden_hit legs; deterministic, seconds).
|
|
152
|
+
test_body_compliance_grading.sh
|
|
140
153
|
test_regression_runner_registration.sh
|
|
141
154
|
# Harness binding for the gate lanes themselves: the impact-chain gate's
|
|
142
155
|
# round walk needs the branch's own first-parent chain, which the default
|
|
@@ -149,6 +162,14 @@ fast_tests=(
|
|
|
149
162
|
test_regression_runner_lanes.sh
|
|
150
163
|
test_routing_pointer_integrity.sh
|
|
151
164
|
test_routing_bank_integrity.sh
|
|
165
|
+
# 077: frozen-case sanctity (regressions-are-sacred) — base-relative guard
|
|
166
|
+
# that a deleted/re-scoped bank case or golden trace names its adjudication
|
|
167
|
+
# row; degrades to an explicit skip token without CCL_SKILL_BASE_REF. The
|
|
168
|
+
# selfproof lane proves the oracle fails for the right reason (synthetic
|
|
169
|
+
# repo: deletion/re-scope/nested-trace mutants red, forged-credit legs red,
|
|
170
|
+
# adjudicated and control legs green).
|
|
171
|
+
test_frozen_case_sanctity.sh
|
|
172
|
+
test_frozen_case_sanctity_selfproof.sh
|
|
152
173
|
test_uiux_delivery_contract.sh
|
|
153
174
|
test_uiux_loading_budget.sh
|
|
154
175
|
test_governing_chain_diff.sh
|
|
@@ -195,6 +216,13 @@ heavy_tests=(
|
|
|
195
216
|
# 「跳过义务会不会留下可检出的缺席」——044 连续五轮 challenge 都停在这一点上,负向
|
|
196
217
|
# 用例(缺证据、非枚举值、降范围无留痕)必须每轮都跑,否则触发器会悄悄退化成声称。
|
|
197
218
|
test_impact_chain_self_adjudication.sh
|
|
219
|
+
# 074: one applied deletion mutation per pinned-phrase gate family, each leg
|
|
220
|
+
# running the FULL shipped checker against a committed fixture copy of the
|
|
221
|
+
# working tree (6 full-gate runs — far too slow for pre-commit). Proves the
|
|
222
|
+
# ~40 required_phrase pins and the contract-anchor delegation can actually
|
|
223
|
+
# go red for the right reason; the fast lane keeps the checker-level
|
|
224
|
+
# self-proof in test_check_contract_anchors.sh.
|
|
225
|
+
test_pinned_phrase_mutation_walk.sh
|
|
198
226
|
)
|
|
199
227
|
|
|
200
228
|
# Registration self-audit: every sibling test_*.sh must appear in fast_tests or
|
|
@@ -817,6 +817,257 @@ assert_contains "entrypoint_word_budget_block_partial: skills/demo-skill/SKILL.m
|
|
|
817
817
|
assert_contains "entrypoint_word_budget_blocking_failed" "$out" "word-budget partial emits failed marker"
|
|
818
818
|
assert_not_contains "entrypoint_word_budget_blocking_ok" "$out" "unknown baseline never earns a word-budget pass"
|
|
819
819
|
|
|
820
|
+
# ---------------------------------------------------------------------------
|
|
821
|
+
# Reference-file line ratchet (fresh repo): a NEW or crossing reference over 500
|
|
822
|
+
# physical lines blocks; a historical over-limit reference may stay level or
|
|
823
|
+
# shrink but not grow; the append-only ledger is excluded by gate design; an
|
|
824
|
+
# unresolvable base fails closed; a long new reference without ## structure
|
|
825
|
+
# draws an advisory, never a block.
|
|
826
|
+
REF_REPO="$TMP/repo-reference-lines"
|
|
827
|
+
mkdir -p "$REF_REPO/skills/demo-skill/references" "$REF_REPO/skills/legacy-skill/references"
|
|
828
|
+
git init -q -b main "$REF_REPO"
|
|
829
|
+
git -C "$REF_REPO" config user.email test@example.invalid
|
|
830
|
+
git -C "$REF_REPO" config user.name "Test User"
|
|
831
|
+
|
|
832
|
+
write_skill_stub() { # <skill-dir>
|
|
833
|
+
{
|
|
834
|
+
printf -- '---\nname: %s\ndescription: reference line-ratchet fixture.\n---\n\n' "$(basename "$1")"
|
|
835
|
+
printf -- 'Body pointer to references.\n'
|
|
836
|
+
} > "$1/SKILL.md"
|
|
837
|
+
}
|
|
838
|
+
write_reference_lines() { # <path> <line-count> [--no-headings]
|
|
839
|
+
local n="$2" i
|
|
840
|
+
: > "$1"
|
|
841
|
+
if [ "${3:-}" != "--no-headings" ]; then
|
|
842
|
+
printf -- '# Fixture reference\n\n## Section one\n' >> "$1"
|
|
843
|
+
i=3
|
|
844
|
+
else
|
|
845
|
+
i=0
|
|
846
|
+
fi
|
|
847
|
+
while [ "$i" -lt "$n" ]; do
|
|
848
|
+
printf -- 'line %s\n' "$i" >> "$1"
|
|
849
|
+
i=$((i + 1))
|
|
850
|
+
done
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
write_skill_stub "$REF_REPO/skills/demo-skill"
|
|
854
|
+
write_skill_stub "$REF_REPO/skills/legacy-skill"
|
|
855
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/small.md" 40
|
|
856
|
+
write_reference_lines "$REF_REPO/skills/legacy-skill/references/legacy.md" 620
|
|
857
|
+
git -C "$REF_REPO" add -A
|
|
858
|
+
git -C "$REF_REPO" commit -qm "reference baseline: one small, one historical over-limit"
|
|
859
|
+
git -C "$REF_REPO" checkout -qb feature
|
|
860
|
+
|
|
861
|
+
run_ref() { # runs with a guaranteed-unset base ref
|
|
862
|
+
set +e
|
|
863
|
+
out="$(env -u CCL_SKILL_BASE_REF bash "$SIZE_SCRIPT" "$REF_REPO" 2>&1)"
|
|
864
|
+
rc=$?
|
|
865
|
+
set -e
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
# Case g0: nothing changed => clean counts and the reference verdict token. The
|
|
869
|
+
# historical over-limit file is visible in the counter but reddens nothing.
|
|
870
|
+
run_ref
|
|
871
|
+
assert_rc "$rc" 0 "untouched reference corpus must pass"
|
|
872
|
+
assert_contains "reference_line_over_limit_count_base=1" "$out" "base over-limit count available"
|
|
873
|
+
assert_contains "reference_line_over_limit_count_head=1" "$out" "head over-limit count available"
|
|
874
|
+
assert_contains "reference_line_over_limit_count_delta=+0" "$out" "over-limit delta is signed"
|
|
875
|
+
assert_contains "reference_line_budget_blocking_ok" "$out" "clean reference verdict token"
|
|
876
|
+
assert_not_contains "reference_line_block" "$out" "an untouched over-limit reference must not red an unrelated change"
|
|
877
|
+
|
|
878
|
+
# Case g1: a NEW reference over the line budget blocks with path, actual, and
|
|
879
|
+
# allowed counts.
|
|
880
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/oversized.md" 501
|
|
881
|
+
run_ref
|
|
882
|
+
assert_rc "$rc" 1 "new over-budget reference must block"
|
|
883
|
+
assert_contains "reference_line_block: skills/demo-skill/references/oversized.md: new reference over line budget" "$out" "block names the file"
|
|
884
|
+
assert_contains "head_lines=501" "$out" "block cites the actual line count"
|
|
885
|
+
assert_contains "(> 500)" "$out" "block cites the allowed line count"
|
|
886
|
+
assert_contains "reference_line_budget_blocking_failed" "$out" "reference failure marker present"
|
|
887
|
+
assert_not_contains "reference_line_budget_blocking_ok" "$out" "no false ok next to a reference block"
|
|
888
|
+
assert_not_contains "entrypoint_size_budget_advisory_ok" "$out" "no legacy ok next to a reference failure"
|
|
889
|
+
|
|
890
|
+
# Case g1b: exactly at the budget is legal (the boundary is > 500, not >= 500).
|
|
891
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/oversized.md" 500
|
|
892
|
+
run_ref
|
|
893
|
+
assert_rc "$rc" 0 "a reference exactly at the budget must land"
|
|
894
|
+
assert_contains "reference_line_budget_blocking_ok" "$out" "boundary case keeps the clean verdict"
|
|
895
|
+
rm -f "$REF_REPO/skills/demo-skill/references/oversized.md"
|
|
896
|
+
|
|
897
|
+
# Case g2: an existing within-limit reference that CROSSES the budget blocks on
|
|
898
|
+
# the same new-or-crossing branch.
|
|
899
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/small.md" 640
|
|
900
|
+
run_ref
|
|
901
|
+
assert_rc "$rc" 1 "a reference crossing the budget must block"
|
|
902
|
+
assert_contains "reference_line_block: skills/demo-skill/references/small.md: new reference over line budget" "$out" "crossing block names the file"
|
|
903
|
+
git -C "$REF_REPO" checkout -- skills/demo-skill/references/small.md
|
|
904
|
+
|
|
905
|
+
# Case g3: a historical over-limit reference may SHRINK and may stay LEVEL.
|
|
906
|
+
write_reference_lines "$REF_REPO/skills/legacy-skill/references/legacy.md" 600
|
|
907
|
+
run_ref
|
|
908
|
+
assert_rc "$rc" 0 "shrinking a historical over-limit reference must land"
|
|
909
|
+
assert_contains "reference_line_budget_legacy_ok: skills/legacy-skill/references/legacy.md" "$out" "legacy allowance token emitted"
|
|
910
|
+
assert_contains "allowed_lines=620" "$out" "legacy allowance is frozen at the base measure"
|
|
911
|
+
assert_contains "delta_lines=-" "$out" "per-file reference delta is negative"
|
|
912
|
+
|
|
913
|
+
# Case g3b: a same-line-count content edit of a historical over-limit reference
|
|
914
|
+
# lands. A shrink leg alone cannot see a `>` turning into `>=`, which would block
|
|
915
|
+
# an author who only rewrites text inside an already-over-limit file. The shrink
|
|
916
|
+
# from the previous leg is restored first so this one measures a true level edit.
|
|
917
|
+
git -C "$REF_REPO" checkout -- skills/legacy-skill/references/legacy.md
|
|
918
|
+
python3 - "$REF_REPO/skills/legacy-skill/references/legacy.md" <<'PY'
|
|
919
|
+
import sys
|
|
920
|
+
p = sys.argv[1]
|
|
921
|
+
lines = open(p, encoding="utf-8").read().splitlines(True)
|
|
922
|
+
lines[5] = "rewritten content at the same line count\n"
|
|
923
|
+
open(p, "w", encoding="utf-8").writelines(lines)
|
|
924
|
+
PY
|
|
925
|
+
run_ref
|
|
926
|
+
assert_rc "$rc" 0 "a same-size content edit of a historical over-limit reference must land"
|
|
927
|
+
assert_contains "reference_line_budget_legacy_ok: skills/legacy-skill/references/legacy.md" "$out" "level edit keeps the legacy allowance"
|
|
928
|
+
assert_contains "delta_lines=+0" "$out" "the level edit really is level"
|
|
929
|
+
git -C "$REF_REPO" checkout -- skills/legacy-skill/references/legacy.md
|
|
930
|
+
|
|
931
|
+
# Case g4: the same historical reference may NOT grow.
|
|
932
|
+
write_reference_lines "$REF_REPO/skills/legacy-skill/references/legacy.md" 621
|
|
933
|
+
run_ref
|
|
934
|
+
assert_rc "$rc" 1 "growing a historical over-limit reference must block"
|
|
935
|
+
assert_contains "reference_line_block: skills/legacy-skill/references/legacy.md: over-limit reference grew" "$out" "growth block names the file"
|
|
936
|
+
assert_contains "base_lines=620" "$out" "growth block cites the frozen baseline"
|
|
937
|
+
assert_contains "head_lines=621" "$out" "growth block cites the head count"
|
|
938
|
+
git -C "$REF_REPO" checkout -- skills/legacy-skill/references/legacy.md
|
|
939
|
+
|
|
940
|
+
# Case g5: the append-only ledger is excluded by gate design — it may exceed the
|
|
941
|
+
# budget and may GROW (append-only rows) without blocking, and says so.
|
|
942
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/source-register.md" 900
|
|
943
|
+
run_ref
|
|
944
|
+
assert_rc "$rc" 0 "the append-only ledger must not be line-capped"
|
|
945
|
+
assert_contains "reference_line_budget_ledger_excluded: skills/demo-skill/references/source-register.md" "$out" "ledger exclusion is reported, not silent"
|
|
946
|
+
assert_not_contains "reference_line_block: skills/demo-skill/references/source-register.md" "$out" "ledger growth never blocks"
|
|
947
|
+
git -C "$REF_REPO" add -A
|
|
948
|
+
git -C "$REF_REPO" commit -qm "land the ledger fixture"
|
|
949
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/source-register.md" 1200
|
|
950
|
+
run_ref
|
|
951
|
+
assert_rc "$rc" 0 "appending rows to the ledger must stay landable"
|
|
952
|
+
assert_not_contains "reference_line_block" "$out" "ledger append is not a growth block"
|
|
953
|
+
git -C "$REF_REPO" checkout -- skills/demo-skill/references/source-register.md
|
|
954
|
+
|
|
955
|
+
# Case g6: a rename of a historical over-limit reference carries ONLY its own
|
|
956
|
+
# allowance, and move-plus-growth blocks.
|
|
957
|
+
git -C "$REF_REPO" mv skills/legacy-skill/references/legacy.md skills/legacy-skill/references/legacy-renamed.md
|
|
958
|
+
run_ref
|
|
959
|
+
assert_rc "$rc" 0 "renaming a historical over-limit reference without growth must pass"
|
|
960
|
+
assert_contains "reference_line_budget_move_ok: skills/legacy-skill/references/legacy-renamed.md" "$out" "rename carries its own historical allowance"
|
|
961
|
+
assert_contains "moved_from=skills/legacy-skill/references/legacy.md" "$out" "move diagnostic proves the rename branch ran"
|
|
962
|
+
write_reference_lines "$REF_REPO/skills/legacy-skill/references/legacy-renamed.md" 621
|
|
963
|
+
run_ref
|
|
964
|
+
assert_rc "$rc" 1 "rename plus growth must block"
|
|
965
|
+
assert_not_contains "reference_line_budget_move_ok" "$out" "move credit must not survive growth"
|
|
966
|
+
git -C "$REF_REPO" reset --hard -q HEAD
|
|
967
|
+
|
|
968
|
+
# Case g7: a changed over-budget reference with an unresolvable base fails
|
|
969
|
+
# closed — losing the baseline must never become a waiver.
|
|
970
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/oversized.md" 700
|
|
971
|
+
set +e
|
|
972
|
+
out="$(env CCL_SKILL_BASE_REF=definitely-not-a-ref bash "$SIZE_SCRIPT" "$REF_REPO" 2>&1)"
|
|
973
|
+
rc=$?
|
|
974
|
+
set -e
|
|
975
|
+
assert_rc "$rc" 1 "over-budget reference with unknown base must fail closed"
|
|
976
|
+
assert_contains "reference_line_block_partial: skills/demo-skill/references/oversized.md: base unknown" "$out" "reference partial names the file"
|
|
977
|
+
assert_contains "reference_line_budget_blocking_failed" "$out" "reference partial emits the failed marker"
|
|
978
|
+
assert_not_contains "reference_line_budget_blocking_ok" "$out" "unknown baseline never earns a reference pass"
|
|
979
|
+
rm -f "$REF_REPO/skills/demo-skill/references/oversized.md"
|
|
980
|
+
|
|
981
|
+
# Case g8: committed-only reference growth with an unresolvable base and a clean
|
|
982
|
+
# tree evaluates nothing, so it must say un-evaluated rather than ok.
|
|
983
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/oversized.md" 700
|
|
984
|
+
git -C "$REF_REPO" add -A
|
|
985
|
+
git -C "$REF_REPO" commit -qm "commit the reference growth so the tree is clean"
|
|
986
|
+
[ -z "$(git -C "$REF_REPO" status --porcelain)" ] || fail "g8 fixture: tree must be clean"
|
|
987
|
+
set +e
|
|
988
|
+
out="$(env CCL_SKILL_BASE_REF=definitely-not-a-ref bash "$SIZE_SCRIPT" "$REF_REPO" 2>&1)"
|
|
989
|
+
rc=$?
|
|
990
|
+
set -e
|
|
991
|
+
assert_rc "$rc" 0 "base-less clean tree keeps the advisory exit contract"
|
|
992
|
+
assert_contains "reference_line_budget_blocking_unevaluated" "$out" "un-evaluated reference gate says so"
|
|
993
|
+
assert_not_contains "reference_line_budget_blocking_ok" "$out" "a base-less run must never claim a reference pass"
|
|
994
|
+
|
|
995
|
+
# Case g9: a NEW reference over 100 lines with no ## structure draws the
|
|
996
|
+
# navigation advisory and does NOT block; a structured one stays quiet. The g8
|
|
997
|
+
# over-budget fixture is retired first so this leg measures only its own files.
|
|
998
|
+
git -C "$REF_REPO" rm -q skills/demo-skill/references/oversized.md
|
|
999
|
+
git -C "$REF_REPO" commit -qm "retire the over-budget reference fixture"
|
|
1000
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/flat.md" 140 --no-headings
|
|
1001
|
+
run_ref
|
|
1002
|
+
assert_rc "$rc" 0 "the navigation advisory must never block"
|
|
1003
|
+
assert_contains "reference_nav_advisory: skills/demo-skill/references/flat.md" "$out" "unstructured long reference draws the advisory"
|
|
1004
|
+
assert_contains "reference_line_budget_blocking_ok" "$out" "advisory leaves the blocking verdict clean"
|
|
1005
|
+
write_reference_lines "$REF_REPO/skills/demo-skill/references/structured.md" 140
|
|
1006
|
+
run_ref
|
|
1007
|
+
assert_rc "$rc" 0 "a structured long reference stays clean"
|
|
1008
|
+
assert_not_contains "reference_nav_advisory: skills/demo-skill/references/structured.md" "$out" "sectioned reference draws no advisory"
|
|
1009
|
+
rm -f "$REF_REPO/skills/demo-skill/references/flat.md" "$REF_REPO/skills/demo-skill/references/structured.md"
|
|
1010
|
+
|
|
1011
|
+
# Case g9b: H3 headings are not section structure for this purpose — a long new
|
|
1012
|
+
# reference whose only headings are `###` still draws the navigation advisory.
|
|
1013
|
+
{ printf -- '# Fixture\n\n### Detail only\n'; i=0; while [ "$i" -lt 140 ]; do printf -- 'line %s\n' "$i"; i=$((i + 1)); done; } > "$REF_REPO/skills/demo-skill/references/h3only.md"
|
|
1014
|
+
run_ref
|
|
1015
|
+
assert_rc "$rc" 0 "the H3-only advisory must never block"
|
|
1016
|
+
assert_contains "reference_nav_advisory: skills/demo-skill/references/h3only.md" "$out" "an H3-only long reference still needs ## sections"
|
|
1017
|
+
rm -f "$REF_REPO/skills/demo-skill/references/h3only.md"
|
|
1018
|
+
|
|
1019
|
+
# Case g10: line endings must not change the measure. A CR-only or CRLF file
|
|
1020
|
+
# measures like the LF file a reader sees, so a 501-line CR-delimited reference
|
|
1021
|
+
# cannot slip through as a single line, and its 500-line sibling still lands.
|
|
1022
|
+
python3 - "$REF_REPO/skills/demo-skill/references/cr-only.md" <<'PY'
|
|
1023
|
+
import sys
|
|
1024
|
+
open(sys.argv[1], "wb").write(b"\r".join(b"line %d" % i for i in range(501)) + b"\r")
|
|
1025
|
+
PY
|
|
1026
|
+
run_ref
|
|
1027
|
+
assert_rc "$rc" 1 "a CR-delimited over-budget reference must block"
|
|
1028
|
+
assert_contains "reference_line_block: skills/demo-skill/references/cr-only.md: new reference over line budget" "$out" "CR-only block names the file"
|
|
1029
|
+
assert_contains "head_lines=501" "$out" "CR-only lines are counted like LF lines"
|
|
1030
|
+
python3 - "$REF_REPO/skills/demo-skill/references/cr-only.md" <<'PY'
|
|
1031
|
+
import sys
|
|
1032
|
+
open(sys.argv[1], "wb").write(b"\r\n".join(b"line %d" % i for i in range(501)) + b"\r\n")
|
|
1033
|
+
PY
|
|
1034
|
+
run_ref
|
|
1035
|
+
assert_rc "$rc" 1 "a CRLF over-budget reference must block"
|
|
1036
|
+
assert_contains "head_lines=501" "$out" "CRLF lines are counted once, not twice"
|
|
1037
|
+
python3 - "$REF_REPO/skills/demo-skill/references/cr-only.md" <<'PY'
|
|
1038
|
+
import sys
|
|
1039
|
+
open(sys.argv[1], "wb").write(b"\r".join(b"line %d" % i for i in range(500)) + b"\r")
|
|
1040
|
+
PY
|
|
1041
|
+
run_ref
|
|
1042
|
+
assert_rc "$rc" 0 "a CR-delimited reference at the budget still lands"
|
|
1043
|
+
assert_contains "reference_line_budget_blocking_ok" "$out" "boundary holds under CR line endings"
|
|
1044
|
+
rm -f "$REF_REPO/skills/demo-skill/references/cr-only.md"
|
|
1045
|
+
|
|
1046
|
+
# Case g11: an unreadable reference degrades the CENSUS COUNTER to unknown and
|
|
1047
|
+
# says so, instead of silently counting it as within budget or aborting the whole
|
|
1048
|
+
# program before the per-file verdicts run. Running as root defeats chmod, so the
|
|
1049
|
+
# leg checks that the fixture is actually unreadable and prints an explicit skip
|
|
1050
|
+
# token otherwise — skipped is not passed.
|
|
1051
|
+
UNREADABLE="$REF_REPO/skills/demo-skill/references/unreadable.md"
|
|
1052
|
+
write_reference_lines "$UNREADABLE" 40
|
|
1053
|
+
git -C "$REF_REPO" add -A
|
|
1054
|
+
git -C "$REF_REPO" commit -qm "land a readable reference to make unreadable"
|
|
1055
|
+
chmod 000 "$UNREADABLE"
|
|
1056
|
+
if head -c 1 "$UNREADABLE" >/dev/null 2>&1; then
|
|
1057
|
+
echo "reference_census_unreadable_leg_skipped: this process can still read a chmod-000 file (likely root) — skipped is not passed"
|
|
1058
|
+
else
|
|
1059
|
+
run_ref
|
|
1060
|
+
assert_rc "$rc" 1 "an unreadable CHANGED reference fails closed"
|
|
1061
|
+
assert_contains "reference_line_census_partial" "$out" "the census says it is incomplete"
|
|
1062
|
+
assert_contains "reference_line_over_limit_count_head=unknown" "$out" "an incomplete census reports unknown, never a count"
|
|
1063
|
+
assert_contains "reference_line_over_limit_count_delta=unknown" "$out" "an unknown head count cannot produce a signed delta"
|
|
1064
|
+
assert_contains "reference_line_block_partial: skills/demo-skill/references/unreadable.md" "$out" "the per-file verdict still runs and names the file"
|
|
1065
|
+
assert_not_contains "reference_line_budget_blocking_ok" "$out" "an unreadable changed reference never earns a pass"
|
|
1066
|
+
fi
|
|
1067
|
+
chmod 644 "$UNREADABLE"
|
|
1068
|
+
git -C "$REF_REPO" rm -q "skills/demo-skill/references/unreadable.md"
|
|
1069
|
+
git -C "$REF_REPO" commit -qm "retire the unreadable fixture"
|
|
1070
|
+
|
|
820
1071
|
# Case e8: gate wiring anchors — the validator must fail the gate on non-zero
|
|
821
1072
|
# and must not demote the size gate back to advisory.
|
|
822
1073
|
validator="$SCRIPT_DIR/check-ccl-skills.sh"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-proof suite for check-contract-anchors.sh.
|
|
3
|
+
#
|
|
4
|
+
# Oracle self-proof tables (the gate is trusted only because every row below
|
|
5
|
+
# was APPLIED and observed):
|
|
6
|
+
#
|
|
7
|
+
# MUST-HIT (mutant -> expected red token, expected rc):
|
|
8
|
+
# M1 pinned literal deleted from anchored file -> contract_anchor_missing rc 1
|
|
9
|
+
# M2 pinned literal duplicated in anchored file -> contract_anchor_duplicate rc 1
|
|
10
|
+
# M3 anchored file removed -> contract_anchor_file_missing rc 1
|
|
11
|
+
# M4 table emptied (comments only) -> contract_anchor_table_empty rc 2
|
|
12
|
+
# M5 malformed row (3 fields) -> contract_anchor_row_malformed rc 2
|
|
13
|
+
# M6 literal under 16 chars -> contract_anchor_literal_too_short rc 2
|
|
14
|
+
# M7 duplicate anchor id -> contract_anchor_duplicate_id rc 2
|
|
15
|
+
# M8 table file missing -> contract_anchor_table_missing rc 2
|
|
16
|
+
# M9 malformed row + drifted anchor in one table -> BOTH reported, rc 2
|
|
17
|
+
# (integrity errors are collected, well-formed rows still evaluated,
|
|
18
|
+
# and a broken table yields no verdict — fail-closed over the drift)
|
|
19
|
+
#
|
|
20
|
+
# MUST-NOT-HIT (benign neighbor -> stays green, rc 0):
|
|
21
|
+
# B1 wording adjacent to the pinned literal edited (same line, outside the pin)
|
|
22
|
+
# B2 unrelated file in the tree edited freely
|
|
23
|
+
# B3 anchored file grows new content elsewhere
|
|
24
|
+
#
|
|
25
|
+
# DIFFERENTIAL ATTRIBUTION:
|
|
26
|
+
# D1 two anchors, one broken -> output names exactly the broken id, rc 1,
|
|
27
|
+
# and the healthy id is not reported
|
|
28
|
+
#
|
|
29
|
+
# REAL-REPO LEG:
|
|
30
|
+
# R1 shipped table against the repo root -> rc 0 and pinned output grammar
|
|
31
|
+
# "contract_anchor_gate_ok (<N> anchors)"
|
|
32
|
+
set -u
|
|
33
|
+
|
|
34
|
+
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
|
35
|
+
checker="$script_dir/check-contract-anchors.sh"
|
|
36
|
+
repo_root=$(cd "$script_dir/../../.." && pwd -P)
|
|
37
|
+
fail=0
|
|
38
|
+
|
|
39
|
+
note() { printf '%s\n' "$*"; }
|
|
40
|
+
expect() { # expect <case> <want_rc> <got_rc> <want_token> <output>
|
|
41
|
+
local case_id="$1" want_rc="$2" got_rc="$3" want_token="$4" output="$5"
|
|
42
|
+
if [[ "$got_rc" -ne "$want_rc" ]]; then
|
|
43
|
+
note "FAIL $case_id: rc=$got_rc want=$want_rc"
|
|
44
|
+
note "$output"
|
|
45
|
+
fail=1
|
|
46
|
+
return
|
|
47
|
+
fi
|
|
48
|
+
if [[ -n "$want_token" && "$output" != *"$want_token"* ]]; then
|
|
49
|
+
note "FAIL $case_id: missing token '$want_token' in output"
|
|
50
|
+
note "$output"
|
|
51
|
+
fail=1
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
note "ok $case_id"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
tmp=$(mktemp -d)
|
|
58
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
59
|
+
|
|
60
|
+
make_fixture() { # rebuild a fresh fixture tree + table under $tmp/fx
|
|
61
|
+
rm -rf "$tmp/fx"
|
|
62
|
+
mkdir -p "$tmp/fx/docs"
|
|
63
|
+
cat > "$tmp/fx/docs/sample-contract.md" <<'MD'
|
|
64
|
+
# Sample contract
|
|
65
|
+
|
|
66
|
+
The sample verdict is decided by exactly one discriminating predicate here.
|
|
67
|
+
Unrelated surrounding prose that may change freely.
|
|
68
|
+
MD
|
|
69
|
+
cat > "$tmp/fx/docs/other.md" <<'MD'
|
|
70
|
+
Free-floating document with no anchors at all.
|
|
71
|
+
MD
|
|
72
|
+
printf '%s\t%s\t%s\t%s\n' \
|
|
73
|
+
"sample-discriminator" "docs/sample-contract.md" \
|
|
74
|
+
"decided by exactly one discriminating predicate" "fixture row" \
|
|
75
|
+
> "$tmp/fx/anchors.tsv"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
run() { # run <root> <table>; sets got_rc/got_out
|
|
79
|
+
got_out=$(bash "$checker" "$1" --table "$2" 2>&1)
|
|
80
|
+
got_rc=$?
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# R1: shipped table against the real repo
|
|
84
|
+
run "$repo_root" "$script_dir/contract-anchors.tsv"
|
|
85
|
+
expect R1 0 "$got_rc" "contract_anchor_gate_ok (" "$got_out"
|
|
86
|
+
if ! grep -qE '^contract_anchor_gate_ok \([0-9]+ anchors\)$' <<<"$got_out"; then
|
|
87
|
+
note "FAIL R1-grammar: green output grammar drifted: $got_out"
|
|
88
|
+
fail=1
|
|
89
|
+
else
|
|
90
|
+
note "ok R1-grammar"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Green fixture control (precondition for every mutant below)
|
|
94
|
+
make_fixture
|
|
95
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
96
|
+
expect C0 0 "$got_rc" "contract_anchor_gate_ok (1 anchors)" "$got_out"
|
|
97
|
+
|
|
98
|
+
# M1 literal deleted
|
|
99
|
+
make_fixture
|
|
100
|
+
perl -pi -e 's/decided by exactly one discriminating predicate/decided case by case/' "$tmp/fx/docs/sample-contract.md"
|
|
101
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
102
|
+
expect M1 1 "$got_rc" "contract_anchor_missing: sample-discriminator" "$got_out"
|
|
103
|
+
|
|
104
|
+
# M2 literal duplicated
|
|
105
|
+
make_fixture
|
|
106
|
+
printf '\nA second copy: decided by exactly one discriminating predicate.\n' >> "$tmp/fx/docs/sample-contract.md"
|
|
107
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
108
|
+
expect M2 1 "$got_rc" "contract_anchor_duplicate: sample-discriminator" "$got_out"
|
|
109
|
+
|
|
110
|
+
# M3 anchored file removed
|
|
111
|
+
make_fixture
|
|
112
|
+
rm "$tmp/fx/docs/sample-contract.md"
|
|
113
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
114
|
+
expect M3 1 "$got_rc" "contract_anchor_file_missing: sample-discriminator" "$got_out"
|
|
115
|
+
|
|
116
|
+
# M4 table emptied
|
|
117
|
+
make_fixture
|
|
118
|
+
printf '# only comments\n\n' > "$tmp/fx/anchors.tsv"
|
|
119
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
120
|
+
expect M4 2 "$got_rc" "contract_anchor_table_empty" "$got_out"
|
|
121
|
+
|
|
122
|
+
# M5 malformed row
|
|
123
|
+
make_fixture
|
|
124
|
+
printf 'bad-row\tdocs/sample-contract.md\tonly three fields\n' > "$tmp/fx/anchors.tsv"
|
|
125
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
126
|
+
expect M5 2 "$got_rc" "contract_anchor_row_malformed" "$got_out"
|
|
127
|
+
|
|
128
|
+
# M6 short literal
|
|
129
|
+
make_fixture
|
|
130
|
+
printf 'short\tdocs/sample-contract.md\ttiny pin\tnote\n' > "$tmp/fx/anchors.tsv"
|
|
131
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
132
|
+
expect M6 2 "$got_rc" "contract_anchor_literal_too_short" "$got_out"
|
|
133
|
+
|
|
134
|
+
# M7 duplicate id
|
|
135
|
+
make_fixture
|
|
136
|
+
printf '%s\t%s\t%s\t%s\n' "sample-discriminator" "docs/sample-contract.md" \
|
|
137
|
+
"Unrelated surrounding prose that may change" "second row same id" >> "$tmp/fx/anchors.tsv"
|
|
138
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
139
|
+
expect M7 2 "$got_rc" "contract_anchor_duplicate_id" "$got_out"
|
|
140
|
+
|
|
141
|
+
# M8 table missing
|
|
142
|
+
make_fixture
|
|
143
|
+
run "$tmp/fx" "$tmp/fx/missing.tsv"
|
|
144
|
+
expect M8 2 "$got_rc" "contract_anchor_table_missing" "$got_out"
|
|
145
|
+
|
|
146
|
+
# M9 malformed row + drifted anchor: both reported in one run, rc 2
|
|
147
|
+
make_fixture
|
|
148
|
+
printf 'bad-row\tdocs/sample-contract.md\tonly three fields\n' >> "$tmp/fx/anchors.tsv"
|
|
149
|
+
perl -pi -e 's/decided by exactly one discriminating predicate/decided informally/' "$tmp/fx/docs/sample-contract.md"
|
|
150
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
151
|
+
expect M9 2 "$got_rc" "contract_anchor_table_invalid" "$got_out"
|
|
152
|
+
if [[ "$got_out" != *"contract_anchor_row_malformed"* || "$got_out" != *"contract_anchor_missing: sample-discriminator"* ]]; then
|
|
153
|
+
note "FAIL M9-completeness: expected both the integrity error and the drift finding in one run"
|
|
154
|
+
note "$got_out"
|
|
155
|
+
fail=1
|
|
156
|
+
else
|
|
157
|
+
note "ok M9-completeness"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# B1 benign neighbor: same line, outside the pinned span
|
|
161
|
+
make_fixture
|
|
162
|
+
perl -pi -e 's/predicate here\./predicate here, with a clarifying tail./' "$tmp/fx/docs/sample-contract.md"
|
|
163
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
164
|
+
expect B1 0 "$got_rc" "contract_anchor_gate_ok" "$got_out"
|
|
165
|
+
|
|
166
|
+
# B2 unrelated file edited
|
|
167
|
+
make_fixture
|
|
168
|
+
printf 'More free text.\n' >> "$tmp/fx/docs/other.md"
|
|
169
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
170
|
+
expect B2 0 "$got_rc" "contract_anchor_gate_ok" "$got_out"
|
|
171
|
+
|
|
172
|
+
# B3 anchored file grows elsewhere
|
|
173
|
+
make_fixture
|
|
174
|
+
printf '\n## New unrelated section\n\nNew prose.\n' >> "$tmp/fx/docs/sample-contract.md"
|
|
175
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
176
|
+
expect B3 0 "$got_rc" "contract_anchor_gate_ok" "$got_out"
|
|
177
|
+
|
|
178
|
+
# D1 differential attribution: two anchors, one broken
|
|
179
|
+
make_fixture
|
|
180
|
+
printf '%s\t%s\t%s\t%s\n' "healthy-anchor" "docs/sample-contract.md" \
|
|
181
|
+
"Unrelated surrounding prose that may change freely." "healthy row" >> "$tmp/fx/anchors.tsv"
|
|
182
|
+
perl -pi -e 's/decided by exactly one discriminating predicate/decided informally/' "$tmp/fx/docs/sample-contract.md"
|
|
183
|
+
run "$tmp/fx" "$tmp/fx/anchors.tsv"
|
|
184
|
+
expect D1 1 "$got_rc" "contract_anchor_missing: sample-discriminator" "$got_out"
|
|
185
|
+
if [[ "$got_out" == *"healthy-anchor"* ]]; then
|
|
186
|
+
note "FAIL D1-attribution: healthy anchor reported in failure output"
|
|
187
|
+
fail=1
|
|
188
|
+
else
|
|
189
|
+
note "ok D1-attribution"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
if [[ "$fail" -ne 0 ]]; then
|
|
193
|
+
echo "test_check_contract_anchors: FAIL"
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
echo "test_check_contract_anchors: ok"
|