@ccoalm/ccl-skills 0.7.0 → 0.9.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.
Files changed (127) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +8 -7
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +6 -1
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +16 -17
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +1 -1
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +6 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +195 -7
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +3 -3
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +13 -5
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +9 -3
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +9 -3
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/normalize_review_timeout.sh +22 -0
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +9 -3
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +1540 -129
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +8 -3
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +76 -1
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +1858 -3
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_update_review_plan_intent.sh +789 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/update_review_plan_intent.py +513 -0
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +1 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/SKILL.md +3 -1
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/architecture-playbook.md +1 -1
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/multi-tenant-isolation.md +1 -1
  24. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +4 -1
  25. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/state-machine-task-patterns.md +2 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +2 -1
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/inference-capacity-operations.md +24 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/llm-client-gateway.md +1 -1
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +4 -1
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +11 -10
  31. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/contracts-and-state.md +5 -0
  32. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +64 -0
  33. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/agents/openai.yaml +4 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +73 -0
  35. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/runtime-and-project-contract.md +58 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +41 -0
  37. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/verification-diagnostics-and-security.md +63 -0
  38. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +3 -2
  39. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/metrics-conventions.md +8 -1
  40. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/sli-slo-design.md +2 -2
  41. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/canary-and-rollout-strategy.md +16 -2
  42. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/promotion-gate-and-review.md +9 -0
  43. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +14 -16
  44. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/code-review-checklist.md +4 -0
  45. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
  46. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +10 -14
  47. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +1 -0
  48. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +1 -1
  49. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +135 -86
  50. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +66 -80
  51. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/delivery-contract.md +275 -0
  52. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +88 -214
  53. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +2 -2
  54. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +10 -8
  55. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +6 -5
  56. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +112 -95
  57. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +30 -21
  58. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +22 -3
  59. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +20 -17
  60. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +7 -9
  61. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +14 -10
  62. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +2 -0
  63. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +3 -3
  64. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +9 -6
  65. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +3 -0
  66. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +37 -10
  67. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +8 -1
  68. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +16 -5
  69. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +16 -5
  70. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +4 -2
  71. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/multi-tenant-isolation.md +1 -1
  72. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +4 -1
  73. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/state-machine-task-patterns.md +2 -0
  74. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/SKILL.md +1 -1
  75. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +8 -8
  76. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +4 -0
  77. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +104 -5
  78. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +11 -9
  79. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +102 -0
  80. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +103 -0
  81. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +20 -0
  82. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +6 -6
  83. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +4 -3
  84. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +69 -2
  85. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +22 -0
  86. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +49 -4
  87. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/obligation-ledger.py +2748 -0
  88. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +20 -5
  89. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/shared_git_surface_gate.py +1142 -0
  90. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +17 -0
  91. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +41 -4
  92. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ci_checkout_ref_binding.sh +120 -0
  93. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_entrypoint_domain_scan_terms.sh +82 -8
  94. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +336 -0
  95. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +82 -10
  96. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger.sh +1416 -0
  97. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger_repo_audit.sh +57 -0
  98. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +141 -4
  99. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +3 -1
  100. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_shared_git_surface_gate.sh +1696 -0
  101. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_delivery_contract.sh +2117 -0
  102. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_loading_budget.sh +316 -0
  103. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +1176 -0
  104. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +31 -1
  105. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +9 -4
  106. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +980 -0
  107. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +8 -6
  108. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/classical-test-design-techniques.md +1 -1
  109. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-review-and-prioritization.md +1 -1
  110. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/update-lifecycle.md +2 -0
  111. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +16 -15
  112. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +5 -1
  113. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +10 -2
  114. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/e2e-real-flow-testing.md +2 -2
  115. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/integration-contract-testing.md +10 -0
  116. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-code-authoring-patterns.md +2 -2
  117. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-topology-and-commands.md +1 -1
  118. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +2 -1
  119. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/annotation-driven-revision.md +9 -0
  120. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/figure-and-table-craft.md +8 -2
  121. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +7 -5
  122. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +1 -1
  123. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/react-architecture.md +3 -0
  124. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-quality-release.md +37 -4
  125. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-ui-quality.md +10 -1
  126. package/dist/assets/release.json +215 -105
  127. package/package.json +1 -1
@@ -0,0 +1,789 @@
1
+ #!/usr/bin/env bash
2
+ # Behavior regression for the canonical review-plan intent updater.
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
6
+ UPDATER="$SCRIPT_DIR/update_review_plan_intent.py"
7
+ GATE="$SCRIPT_DIR/review_gate.py"
8
+ ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd -P)"
9
+ [ -f "$UPDATER" ] || { echo "FAIL: updater not found: $UPDATER" >&2; exit 1; }
10
+ [ -f "$GATE" ] || { echo "FAIL: review gate not found: $GATE" >&2; exit 1; }
11
+
12
+ TMP="$(mktemp -d "${TMPDIR:-/tmp}/review-plan-intent.XXXXXX")"
13
+ trap 'rm -rf "$TMP"' EXIT
14
+
15
+ fail() { echo "FAIL: $*" >&2; exit 1; }
16
+ assert_rc() { [ "$1" = "$2" ] || fail "expected rc=$2 got rc=$1 ($3)"; }
17
+ assert_contains() { case "$2" in *"$1"*) : ;; *) fail "expected '$1' ($3): $2";; esac; }
18
+ digest() { python3 - "$1" <<'PY'
19
+ import hashlib
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest())
24
+ PY
25
+ }
26
+
27
+ PLAN="$TMP/review-plan.json"
28
+ APPEND="$TMP/append.txt"
29
+ CORE="$TMP/core.txt"
30
+ LATEST="$TMP/latest.txt"
31
+ OLD_INTENT="$TMP/old-intent.txt"
32
+
33
+ python3 - "$PLAN" "$APPEND" "$CORE" "$LATEST" "$OLD_INTENT" <<'PY'
34
+ import json
35
+ import hashlib
36
+ import sys
37
+ from pathlib import Path
38
+
39
+ plan_path, append_path, core_path, latest_path, old_intent_path = map(
40
+ Path, sys.argv[1:]
41
+ )
42
+ old = "scope:" + ("x" * (3995 - len("scope:") - len("c27"))) + "c27"
43
+ latest = "latest-round:c28"
44
+ core = old[: 3892 - len("\n\n") - len(latest)]
45
+ replacement = f"{core}\n\n{latest}"
46
+ assert len(old) == 3995 and old.endswith("c27")
47
+ assert len(replacement) == 3892 and old.startswith(core)
48
+ plan = {
49
+ "intent": old,
50
+ "acceptance": ["The candidate behavior is verified."],
51
+ "self_review": [
52
+ {
53
+ "concern": concern,
54
+ "skill": "code-review",
55
+ "conclusion": conclusion,
56
+ "evidence_refs": ["focused-test"],
57
+ }
58
+ for concern, conclusion in (
59
+ ("correctness", "The focused checks cover the updater's accepted state transitions."),
60
+ ("safety", "The focused checks cover no-write failures and file integrity boundaries."),
61
+ ("failure_paths", "The focused checks cover overflow, stale input, and malformed text paths."),
62
+ ("tests_evidence", "The focused regression fails when bounded update guarantees are removed."),
63
+ ("compatibility", "The focused checks preserve the plan schema and original file permissions."),
64
+ )
65
+ ],
66
+ "evidence": [
67
+ {
68
+ "id": "focused-test",
69
+ "result": "The focused updater regression covers overflow and replacement behavior.",
70
+ },
71
+ {
72
+ "id": "review-plan-intent-stable-core-v1",
73
+ "result": (
74
+ f"chars={len(core)};sha256="
75
+ f"{hashlib.sha256(core.encode('utf-8')).hexdigest()}"
76
+ ),
77
+ },
78
+ ],
79
+ }
80
+ plan_path.write_text(json.dumps(plan, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
81
+ append_path.write_text("\nlatest-round:c28", encoding="utf-8")
82
+ core_path.write_text(core, encoding="utf-8")
83
+ latest_path.write_text(latest, encoding="utf-8")
84
+ old_intent_path.write_text(old, encoding="utf-8")
85
+ PY
86
+ chmod 600 "$PLAN"
87
+
88
+ before="$(digest "$PLAN")"
89
+ set +e
90
+ out="$(python3 "$UPDATER" --plan "$PLAN" --append-intent-file "$APPEND" 2>&1)"
91
+ rc=$?
92
+ set -e
93
+ assert_rc "$rc" 2 "3995-character append must fail closed"
94
+ assert_contains "intent_append_overflow" "$out" "stable overflow reason"
95
+ [ "$(digest "$PLAN")" = "$before" ] || fail "overflow changed the plan"
96
+
97
+ # Duplicate JSON keys must fail before any rewrite. Otherwise json.loads keeps
98
+ # only the last value and the updater permanently erases bytes while claiming
99
+ # that intent compaction is loss-preserving.
100
+ python3 - "$PLAN" "$TMP/duplicate-plan.json" "$TMP/duplicate-append.txt" <<'PY'
101
+ import json
102
+ import sys
103
+ from pathlib import Path
104
+
105
+ source, target, append = map(Path, sys.argv[1:])
106
+ plan = json.loads(source.read_text(encoding="utf-8"))
107
+ plan["intent"] = "short stable intent"
108
+ rendered = json.dumps(plan, ensure_ascii=False, indent=2) + "\n"
109
+ rendered = rendered.replace(
110
+ ' "acceptance":',
111
+ ' "acceptance": ["discarded duplicate"],\n "acceptance":',
112
+ 1,
113
+ )
114
+ target.write_text(rendered, encoding="utf-8")
115
+ append.write_text(" next transition", encoding="utf-8")
116
+ PY
117
+ duplicate_before="$(digest "$TMP/duplicate-plan.json")"
118
+ set +e
119
+ out="$(python3 "$UPDATER" --plan "$TMP/duplicate-plan.json" \
120
+ --append-intent-file "$TMP/duplicate-append.txt" \
121
+ --expected-sha256 "$duplicate_before" 2>&1)"
122
+ rc=$?
123
+ set -e
124
+ assert_rc "$rc" 2 "duplicate JSON keys must fail closed"
125
+ assert_contains "plan_duplicate_key" "$out" "duplicate-key reason"
126
+ [ "$(digest "$TMP/duplicate-plan.json")" = "$duplicate_before" ] \
127
+ || fail "duplicate-key refusal changed the plan"
128
+
129
+ set +e
130
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --latest-intent-file "$LATEST" --expected-sha256 "$before" 2>&1)"
131
+ rc=$?
132
+ set -e
133
+ assert_rc "$rc" 0 "compact core+latest update should succeed"
134
+ assert_contains "review_plan_intent_updated" "$out" "success token"
135
+ [ "$(digest "$PLAN")" != "$before" ] || fail "replacement did not change the plan digest"
136
+ # GNU-first probe (mirrors opencode_review.sh): GNU stat echoes unknown BSD
137
+ # directives verbatim with exit 0, so a BSD-first fallback never fires on CI.
138
+ [ "$(stat -c '%a' "$PLAN" 2>/dev/null || stat -f '%Lp' "$PLAN")" = 600 ] || fail "plan mode was not preserved"
139
+ python3 - "$PLAN" "$OLD_INTENT" <<'PY'
140
+ import base64
141
+ import hashlib
142
+ import json
143
+ import sys
144
+ from pathlib import Path
145
+
146
+ plan = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
147
+ core = Path(sys.argv[1]).with_name("core.txt").read_text(encoding="utf-8")
148
+ latest = Path(sys.argv[1]).with_name("latest.txt").read_text(encoding="utf-8")
149
+ old_intent = Path(sys.argv[2]).read_text(encoding="utf-8")
150
+ assert plan["intent"] == f"{core}\n\n{latest}"
151
+ assert len(plan["intent"]) == 3892
152
+ assert set(plan) == {"intent", "acceptance", "self_review", "evidence"}
153
+
154
+ history_prefix = "review-plan-intent-history-v1-"
155
+ manifests = [
156
+ row
157
+ for row in plan["evidence"]
158
+ if row.get("id", "").startswith(history_prefix)
159
+ and row["id"].endswith("-manifest")
160
+ ]
161
+ assert len(manifests) == 1, plan["evidence"]
162
+ manifest = manifests[0]
163
+ manifest_fields = dict(
164
+ field.split("=", 1) for field in manifest["result"].split(";")
165
+ )
166
+ group = manifest["id"][len(history_prefix) : -len("-manifest")]
167
+ parts = sorted(
168
+ (
169
+ row
170
+ for row in plan["evidence"]
171
+ if row.get("id", "").startswith(f"{history_prefix}{group}-part-")
172
+ ),
173
+ key=lambda row: row["id"],
174
+ )
175
+ assert len(parts) == int(manifest_fields["parts"]), parts
176
+ encoded_suffix = "".join(
177
+ row["result"].split(";data=", 1)[1] for row in parts
178
+ )
179
+ suffix = base64.b64decode(encoded_suffix, validate=True).decode("utf-8")
180
+ preserved_old_intent = core + suffix
181
+ assert preserved_old_intent == old_intent
182
+ assert manifest_fields["format"] == "review-plan-intent-history-v1"
183
+ assert manifest_fields["encoding"] == "base64-utf8"
184
+ assert int(manifest_fields["old_chars"]) == len(old_intent)
185
+ assert int(manifest_fields["core_chars"]) == len(core)
186
+ assert manifest_fields["old_sha256"] == hashlib.sha256(
187
+ old_intent.encode("utf-8")
188
+ ).hexdigest()
189
+ assert int(manifest_fields["suffix_bytes"]) == len(suffix.encode("utf-8"))
190
+ assert manifest_fields["suffix_sha256"] == hashlib.sha256(
191
+ suffix.encode("utf-8")
192
+ ).hexdigest()
193
+ PY
194
+
195
+ # Feed the helper's output through the real plan validator. Restrict the client
196
+ # order to the implementer's own family so this proves validation reached the
197
+ # provider-selection boundary without launching an external reviewer.
198
+ printf '%s\n' \
199
+ 'diff --git a/skills/code-review/references/staged-review-contract.md b/skills/code-review/references/staged-review-contract.md' \
200
+ '--- a/skills/code-review/references/staged-review-contract.md' \
201
+ '+++ b/skills/code-review/references/staged-review-contract.md' \
202
+ '@@ -1 +1 @@' \
203
+ '-old contract text' \
204
+ '+new contract text' >"$TMP/candidate.diff"
205
+ set +e
206
+ out="$(CODE_REVIEW_CLIENT_ORDER=codex python3 "$GATE" \
207
+ --mode review \
208
+ --cwd "$ROOT" \
209
+ --diff-file "$TMP/candidate.diff" \
210
+ --implementer-family openai \
211
+ --review-plan-file "$PLAN" \
212
+ --stage build \
213
+ --challenge-budget 0 \
214
+ --timeout 5 \
215
+ --total-timeout 5 2>&1)"
216
+ rc=$?
217
+ set -e
218
+ assert_rc "$rc" 2 "gate-valid plan should reach the no-independent-reviewer boundary"
219
+ assert_contains '"reason_code":"no_independent_reviewer_available"' "$out" "real gate accepted the updated plan"
220
+
221
+ # Compaction is the recovery path when another producer has already written an
222
+ # over-limit intent. It must validate the replacement, not reject the old state
223
+ # before mode dispatch and force an identity-bypassing hand edit.
224
+ python3 - "$PLAN" "$TMP/overlong-plan.json" "$CORE" "$TMP/overlong-latest.txt" <<'PY'
225
+ import json
226
+ import sys
227
+ from pathlib import Path
228
+
229
+ source, target, core_path, latest_path = map(Path, sys.argv[1:])
230
+ plan = json.loads(source.read_text(encoding="utf-8"))
231
+ core = core_path.read_text(encoding="utf-8")
232
+ plan["intent"] = core + ("x" * (4001 - len(core)))
233
+ target.write_text(json.dumps(plan, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
234
+ latest_path.write_text("recover the newest over-limit transition", encoding="utf-8")
235
+ PY
236
+ overlong_before="$(digest "$TMP/overlong-plan.json")"
237
+ set +e
238
+ out="$(python3 "$UPDATER" --plan "$TMP/overlong-plan.json" --compact-core-intent-file "$CORE" --latest-intent-file "$TMP/overlong-latest.txt" --expected-sha256 "$overlong_before" 2>&1)"
239
+ rc=$?
240
+ set -e
241
+ assert_rc "$rc" 0 "an over-limit existing intent must remain compactable"
242
+ assert_contains "review_plan_intent_updated mode=compact" "$out" "over-limit recovery token"
243
+ python3 - "$TMP/overlong-plan.json" "$CORE" "$TMP/overlong-latest.txt" <<'PY'
244
+ import json
245
+ import sys
246
+ from pathlib import Path
247
+
248
+ plan_path, core_path, latest_path = map(Path, sys.argv[1:])
249
+ plan = json.loads(plan_path.read_text(encoding="utf-8"))
250
+ assert plan["intent"] == f"{core_path.read_text(encoding='utf-8')}\n\n{latest_path.read_text(encoding='utf-8')}"
251
+ assert len(plan["intent"]) <= 4000
252
+ PY
253
+
254
+ # Existing plans without a persisted stable-core identity remain appendable,
255
+ # but must not compact an arbitrary prefix into a new history boundary.
256
+ python3 - "$PLAN" "$TMP/legacy-plan.json" <<'PY'
257
+ import json
258
+ import sys
259
+ from pathlib import Path
260
+
261
+ source, target = map(Path, sys.argv[1:])
262
+ plan = json.loads(source.read_text(encoding="utf-8"))
263
+ plan["evidence"] = [
264
+ row
265
+ for row in plan["evidence"]
266
+ if row.get("id") != "review-plan-intent-stable-core-v1"
267
+ ]
268
+ target.write_text(json.dumps(plan, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
269
+ PY
270
+ printf ' legacy-append' >"$TMP/legacy-append.txt"
271
+ legacy_before="$(digest "$TMP/legacy-plan.json")"
272
+ set +e
273
+ out="$(python3 "$UPDATER" --plan "$TMP/legacy-plan.json" --append-intent-file "$TMP/legacy-append.txt" --expected-sha256 "$legacy_before" 2>&1)"
274
+ rc=$?
275
+ set -e
276
+ assert_rc "$rc" 0 "legacy plan append must remain compatible"
277
+ legacy_appended="$(digest "$TMP/legacy-plan.json")"
278
+ python3 - "$TMP/legacy-plan.json" "$TMP/legacy-core.txt" "$TMP/legacy-latest.txt" <<'PY'
279
+ import json
280
+ import sys
281
+ from pathlib import Path
282
+
283
+ plan_path, core_path, latest_path = map(Path, sys.argv[1:])
284
+ intent = json.loads(plan_path.read_text(encoding="utf-8"))["intent"]
285
+ core_path.write_text(intent[:100], encoding="utf-8")
286
+ latest_path.write_text("a genuinely new legacy transition", encoding="utf-8")
287
+ PY
288
+ set +e
289
+ out="$(python3 "$UPDATER" --plan "$TMP/legacy-plan.json" --compact-core-intent-file "$TMP/legacy-core.txt" --latest-intent-file "$TMP/legacy-latest.txt" --expected-sha256 "$legacy_appended" 2>&1)"
290
+ rc=$?
291
+ set -e
292
+ assert_rc "$rc" 2 "legacy plan compaction without identity must fail closed"
293
+ assert_contains "intent_core_identity_missing" "$out" "missing stable-core identity reason"
294
+ [ "$(digest "$TMP/legacy-plan.json")" = "$legacy_appended" ] || fail "identity-free compaction changed the legacy plan"
295
+
296
+ # Pin the exact documented character boundary. A 4001-character append must not
297
+ # change the last valid 4000-character plan.
298
+ python3 - "$APPEND" <<'PY'
299
+ import sys
300
+ from pathlib import Path
301
+
302
+ Path(sys.argv[1]).write_text("z" * 108, encoding="utf-8")
303
+ PY
304
+ expected="$(digest "$PLAN")"
305
+ python3 "$UPDATER" --plan "$PLAN" --append-intent-file "$APPEND" --expected-sha256 "$expected" >/dev/null
306
+ valid_4000="$(digest "$PLAN")"
307
+ printf 'q' >"$APPEND"
308
+ set +e
309
+ out="$(python3 "$UPDATER" --plan "$PLAN" --append-intent-file "$APPEND" --expected-sha256 "$valid_4000" 2>&1)"
310
+ rc=$?
311
+ set -e
312
+ assert_rc "$rc" 2 "4001-character append must fail closed"
313
+ assert_contains "intent_append_overflow" "$out" "append overflow reason"
314
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "append overflow changed the plan"
315
+
316
+ # A prefix is not a stable core merely because it happens to match. The plan's
317
+ # persisted identity binds compaction to the exact core chosen before history
318
+ # accumulated.
319
+ cp "$PLAN" "$TMP/arbitrary-prefix-plan.json"
320
+ python3 - "$TMP/arbitrary-prefix-plan.json" "$TMP/arbitrary-prefix-core.txt" "$TMP/arbitrary-prefix-latest.txt" <<'PY'
321
+ import json
322
+ import sys
323
+ from pathlib import Path
324
+
325
+ plan_path, core_path, latest_path = map(Path, sys.argv[1:])
326
+ intent = json.loads(plan_path.read_text(encoding="utf-8"))["intent"]
327
+ core_path.write_text(intent[:100], encoding="utf-8")
328
+ latest_path.write_text("a novel transition absent from the old intent", encoding="utf-8")
329
+ PY
330
+ prefix_before="$(digest "$TMP/arbitrary-prefix-plan.json")"
331
+ set +e
332
+ out="$(python3 "$UPDATER" --plan "$TMP/arbitrary-prefix-plan.json" --compact-core-intent-file "$TMP/arbitrary-prefix-core.txt" --latest-intent-file "$TMP/arbitrary-prefix-latest.txt" --expected-sha256 "$prefix_before" 2>&1)"
333
+ rc=$?
334
+ set -e
335
+ assert_rc "$rc" 2 "an arbitrary matching prefix must not redefine stable core"
336
+ assert_contains "intent_core_identity_mismatch" "$out" "stable-core identity mismatch reason"
337
+ [ "$(digest "$TMP/arbitrary-prefix-plan.json")" = "$prefix_before" ] || fail "arbitrary-prefix refusal changed the plan"
338
+
339
+ # A digest that was already stale when opened must fail before replacement.
340
+ python3 - "$LATEST" <<'PY'
341
+ import sys
342
+ from pathlib import Path
343
+
344
+ Path(sys.argv[1]).write_text("new compact intent", encoding="utf-8")
345
+ PY
346
+ set +e
347
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --latest-intent-file "$LATEST" --expected-sha256 "$(printf '0%.0s' {1..64})" 2>&1)"
348
+ rc=$?
349
+ set -e
350
+ assert_rc "$rc" 2 "stale expected digest must fail closed"
351
+ assert_contains "plan_digest_mismatch" "$out" "stale-input mismatch reason"
352
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "stale-input mismatch changed the plan"
353
+
354
+ # An intent hand-rewritten outside the helper still starts with the supplied
355
+ # core, but its first core_chars characters no longer hash to the persisted
356
+ # identity; compaction must refuse rather than adopt the rewritten boundary.
357
+ cp "$PLAN" "$TMP/identity-drift-plan.json"
358
+ python3 - "$TMP/identity-drift-plan.json" "$TMP/identity-drift-core.txt" <<'PY'
359
+ import json
360
+ import re
361
+ import sys
362
+ from pathlib import Path
363
+
364
+ plan_path, core_path = map(Path, sys.argv[1:])
365
+ plan = json.loads(plan_path.read_text(encoding="utf-8"))
366
+ identity = next(
367
+ row["result"]
368
+ for row in plan["evidence"]
369
+ if row.get("id") == "review-plan-intent-stable-core-v1"
370
+ )
371
+ chars = int(re.search(r"chars=([0-9]+)", identity).group(1))
372
+ rewritten = "x" * chars + " freshly rewritten tail"
373
+ assert len(rewritten) <= 4000
374
+ plan["intent"] = rewritten
375
+ core_path.write_text(rewritten[:chars], encoding="utf-8")
376
+ plan_path.write_text(
377
+ json.dumps(plan, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
378
+ )
379
+ PY
380
+ printf 'a novel transition for identity drift' >"$TMP/identity-drift-latest.txt"
381
+ drift_before="$(digest "$TMP/identity-drift-plan.json")"
382
+ set +e
383
+ out="$(python3 "$UPDATER" --plan "$TMP/identity-drift-plan.json" --compact-core-intent-file "$TMP/identity-drift-core.txt" --latest-intent-file "$TMP/identity-drift-latest.txt" --expected-sha256 "$drift_before" 2>&1)"
384
+ rc=$?
385
+ set -e
386
+ assert_rc "$rc" 2 "a rewritten intent must not satisfy the persisted identity"
387
+ assert_contains "plan_core_identity_mismatch" "$out" "persisted-identity drift reason"
388
+ [ "$(digest "$TMP/identity-drift-plan.json")" = "$drift_before" ] || fail "identity drift refusal changed the plan"
389
+
390
+ # Zero-loss compaction refuses to overflow the evidence-row cap and leaves the
391
+ # plan byte-identical.
392
+ cp "$PLAN" "$TMP/overflow-rows-plan.json"
393
+ python3 - "$TMP/overflow-rows-plan.json" <<'PY'
394
+ import json
395
+ import sys
396
+ from pathlib import Path
397
+
398
+ plan_path = Path(sys.argv[1])
399
+ plan = json.loads(plan_path.read_text(encoding="utf-8"))
400
+ rows = plan["evidence"]
401
+ index = 0
402
+ while len(rows) < 49:
403
+ rows.append(
404
+ {
405
+ "id": f"synthetic-filler-{index:04d}",
406
+ "result": "synthetic filler evidence row exercising the overflow cap",
407
+ }
408
+ )
409
+ index += 1
410
+ plan_path.write_text(
411
+ json.dumps(plan, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
412
+ )
413
+ PY
414
+ printf 'a novel transition for row overflow' >"$TMP/overflow-rows-latest.txt"
415
+ overflow_rows_before="$(digest "$TMP/overflow-rows-plan.json")"
416
+ set +e
417
+ out="$(python3 "$UPDATER" --plan "$TMP/overflow-rows-plan.json" --compact-core-intent-file "$CORE" --latest-intent-file "$TMP/overflow-rows-latest.txt" --expected-sha256 "$overflow_rows_before" 2>&1)"
418
+ rc=$?
419
+ set -e
420
+ assert_rc "$rc" 2 "history rows beyond the evidence cap must fail closed"
421
+ assert_contains "intent_history_evidence_overflow" "$out" "history-row overflow reason"
422
+ [ "$(digest "$TMP/overflow-rows-plan.json")" = "$overflow_rows_before" ] || fail "row overflow refusal changed the plan"
423
+
424
+ # File transport may contribute one final newline, but arbitrary outer
425
+ # whitespace or repeated blank lines must not be silently stripped into shape.
426
+ python3 - "$PLAN" "$CORE" <<'PY'
427
+ import json
428
+ import sys
429
+ from pathlib import Path
430
+
431
+ plan = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
432
+ Path(sys.argv[2]).write_text(plan["intent"][:100], encoding="utf-8")
433
+ PY
434
+ printf ' leading intent\n' >"$LATEST"
435
+ set +e
436
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --latest-intent-file "$LATEST" --expected-sha256 "$valid_4000" 2>&1)"
437
+ rc=$?
438
+ set -e
439
+ assert_rc "$rc" 2 "outer whitespace must be rejected instead of normalized away"
440
+ assert_contains "intent_latest_not_normalized" "$out" "outer-whitespace reason"
441
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "outer-whitespace refusal changed the plan"
442
+
443
+ printf 'replacement intent\n\n' >"$LATEST"
444
+ set +e
445
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --latest-intent-file "$LATEST" --expected-sha256 "$valid_4000" 2>&1)"
446
+ rc=$?
447
+ set -e
448
+ assert_rc "$rc" 2 "multiple trailing newlines must not be collapsed silently"
449
+ assert_contains "intent_latest_not_normalized" "$out" "repeated-newline reason"
450
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "repeated-newline refusal changed the plan"
451
+
452
+ # Structured compaction requires both components, preserves an exact old prefix,
453
+ # and refuses an already-present transition.
454
+ set +e
455
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --expected-sha256 "$valid_4000" 2>&1)"
456
+ rc=$?
457
+ set -e
458
+ assert_rc "$rc" 2 "core-only compaction must fail closed"
459
+ assert_contains "intent_update_mode_invalid" "$out" "core-only reason"
460
+
461
+ set +e
462
+ out="$(python3 "$UPDATER" --plan "$PLAN" --latest-intent-file "$LATEST" --expected-sha256 "$valid_4000" 2>&1)"
463
+ rc=$?
464
+ set -e
465
+ assert_rc "$rc" 2 "latest-only compaction must fail closed"
466
+ assert_contains "intent_update_mode_invalid" "$out" "latest-only reason"
467
+
468
+ printf 'unrelated stable core' >"$CORE"
469
+ printf 'genuinely new transition' >"$LATEST"
470
+ set +e
471
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --latest-intent-file "$LATEST" --expected-sha256 "$valid_4000" 2>&1)"
472
+ rc=$?
473
+ set -e
474
+ assert_rc "$rc" 2 "unrelated core must fail closed"
475
+ assert_contains "intent_core_not_preserved" "$out" "core-preservation reason"
476
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "bad-core refusal changed the plan"
477
+
478
+ python3 - "$PLAN" "$CORE" "$LATEST" <<'PY'
479
+ import json
480
+ import sys
481
+ from pathlib import Path
482
+
483
+ plan = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
484
+ Path(sys.argv[2]).write_text(plan["intent"][:100], encoding="utf-8")
485
+ Path(sys.argv[3]).write_text("latest-round:c28", encoding="utf-8")
486
+ PY
487
+ set +e
488
+ out="$(python3 "$UPDATER" --plan "$PLAN" --compact-core-intent-file "$CORE" --latest-intent-file "$LATEST" --expected-sha256 "$valid_4000" 2>&1)"
489
+ rc=$?
490
+ set -e
491
+ assert_rc "$rc" 2 "an already-present transition must fail closed"
492
+ assert_contains "intent_latest_not_new" "$out" "old-transition reason"
493
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "old-transition refusal changed the plan"
494
+
495
+ # Empty transport after removing one file delimiter is not a state transition.
496
+ for empty_kind in zero lf crlf; do
497
+ case "$empty_kind" in
498
+ zero) : >"$APPEND" ;;
499
+ lf) printf '\n' >"$APPEND" ;;
500
+ crlf) printf '\r\n' >"$APPEND" ;;
501
+ esac
502
+ set +e
503
+ out="$(python3 "$UPDATER" --plan "$PLAN" --append-intent-file "$APPEND" --expected-sha256 "$valid_4000" 2>&1)"
504
+ rc=$?
505
+ set -e
506
+ assert_rc "$rc" 2 "$empty_kind append must fail closed"
507
+ assert_contains "intent_append_empty" "$out" "$empty_kind empty reason"
508
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "$empty_kind append changed the plan"
509
+ done
510
+
511
+ # The plan itself is opened without following links, and multiply-linked files
512
+ # are rejected before any mutation.
513
+ ln -s "$PLAN" "$TMP/plan-link.json"
514
+ printf 'x' >"$APPEND"
515
+ set +e
516
+ out="$(python3 "$UPDATER" --plan "$TMP/plan-link.json" --append-intent-file "$APPEND" 2>&1)"
517
+ rc=$?
518
+ set -e
519
+ assert_rc "$rc" 2 "symlink plan must fail closed"
520
+ assert_contains "plan_unreadable" "$out" "symlink refusal reason"
521
+ [ "$(digest "$PLAN")" = "$valid_4000" ] || fail "symlink refusal changed the target"
522
+
523
+ cp "$PLAN" "$TMP/hard-plan.json"
524
+ ln "$TMP/hard-plan.json" "$TMP/hard-plan-alias.json"
525
+ set +e
526
+ out="$(python3 "$UPDATER" --plan "$TMP/hard-plan.json" --append-intent-file "$APPEND" 2>&1)"
527
+ rc=$?
528
+ set -e
529
+ assert_rc "$rc" 2 "hardlinked plan must fail closed"
530
+ assert_contains "plan_hardlinked" "$out" "hardlink refusal reason"
531
+
532
+ # Opening a FIFO for reading must not block before the regular-file check.
533
+ mkfifo "$TMP/fifo-plan.json"
534
+ set +e
535
+ out="$(python3 - "$UPDATER" "$TMP/fifo-plan.json" "$APPEND" <<'PY'
536
+ import subprocess
537
+ import sys
538
+
539
+ updater, plan, append = sys.argv[1:]
540
+ try:
541
+ completed = subprocess.run(
542
+ [sys.executable, updater, "--plan", plan, "--append-intent-file", append],
543
+ text=True,
544
+ stdout=subprocess.PIPE,
545
+ stderr=subprocess.STDOUT,
546
+ timeout=1,
547
+ check=False,
548
+ )
549
+ except subprocess.TimeoutExpired as exc:
550
+ raise AssertionError("FIFO plan open blocked") from exc
551
+ assert completed.returncode == 2, completed
552
+ assert "plan_not_regular" in completed.stdout, completed.stdout
553
+ print(completed.stdout, end="")
554
+ PY
555
+ )"
556
+ rc=$?
557
+ set -e
558
+ assert_rc "$rc" 0 "FIFO plan must fail quickly"
559
+ assert_contains "plan_not_regular" "$out" "FIFO regular-file reason"
560
+
561
+ # Character limits count Unicode code points, while the whole plan remains
562
+ # byte-bounded.
563
+ python3 - "$PLAN" "$TMP/unicode-plan.json" <<'PY'
564
+ import json
565
+ import sys
566
+ from pathlib import Path
567
+
568
+ plan = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
569
+ plan["intent"] = "界" * 3999
570
+ Path(sys.argv[2]).write_text(
571
+ json.dumps(plan, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
572
+ )
573
+ PY
574
+ printf '新' >"$APPEND"
575
+ unicode_before="$(digest "$TMP/unicode-plan.json")"
576
+ python3 "$UPDATER" --plan "$TMP/unicode-plan.json" --append-intent-file "$APPEND" --expected-sha256 "$unicode_before" >/dev/null
577
+ python3 - "$TMP/unicode-plan.json" <<'PY'
578
+ import json
579
+ import sys
580
+ from pathlib import Path
581
+
582
+ assert len(json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))["intent"]) == 4000
583
+ PY
584
+ unicode_4000="$(digest "$TMP/unicode-plan.json")"
585
+ set +e
586
+ out="$(python3 "$UPDATER" --plan "$TMP/unicode-plan.json" --append-intent-file "$APPEND" --expected-sha256 "$unicode_4000" 2>&1)"
587
+ rc=$?
588
+ set -e
589
+ assert_rc "$rc" 2 "4001 Unicode characters must fail closed"
590
+ assert_contains "intent_append_overflow" "$out" "Unicode overflow reason"
591
+ [ "$(digest "$TMP/unicode-plan.json")" = "$unicode_4000" ] || fail "Unicode overflow changed the plan"
592
+
593
+ # A character-valid update that would cross the serialized 32,000-byte plan
594
+ # limit is also a no-write failure.
595
+ python3 - "$PLAN" "$TMP/byte-plan.json" "$APPEND" <<'PY'
596
+ import json
597
+ import sys
598
+ from pathlib import Path
599
+
600
+ source, target, append = map(Path, sys.argv[1:])
601
+ base = json.loads(source.read_text(encoding="utf-8"))
602
+ base["intent"] = "small intent"
603
+ append.write_text("a" * 3000, encoding="utf-8")
604
+ for count in range(1, 51):
605
+ base["evidence"] = [
606
+ {"id": f"evidence-{index}", "result": "r" * 600}
607
+ for index in range(count)
608
+ ]
609
+ before = (json.dumps(base, ensure_ascii=False, indent=2) + "\n").encode()
610
+ after_plan = dict(base)
611
+ after_plan["intent"] += "a" * 3000
612
+ after = (json.dumps(after_plan, ensure_ascii=False, indent=2) + "\n").encode()
613
+ if len(before) <= 32_000 < len(after):
614
+ target.write_bytes(before)
615
+ break
616
+ else:
617
+ raise AssertionError("could not construct the 32,000-byte boundary fixture")
618
+ PY
619
+ byte_before="$(digest "$TMP/byte-plan.json")"
620
+ set +e
621
+ out="$(python3 "$UPDATER" --plan "$TMP/byte-plan.json" --append-intent-file "$APPEND" --expected-sha256 "$byte_before" 2>&1)"
622
+ rc=$?
623
+ set -e
624
+ assert_rc "$rc" 2 "serialized plan overflow must fail closed"
625
+ assert_contains "updated_plan_too_large" "$out" "serialized byte-limit reason"
626
+ [ "$(digest "$TMP/byte-plan.json")" = "$byte_before" ] || fail "serialized overflow changed the plan"
627
+
628
+ # A JSON escape may decode to a lone surrogate that cannot be emitted as UTF-8.
629
+ # That is a stable refusal, not a traceback, and leaves the file unchanged.
630
+ python3 - "$PLAN" "$TMP/surrogate-plan.json" <<'PY'
631
+ import json
632
+ import sys
633
+ from pathlib import Path
634
+
635
+ plan = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
636
+ plan["intent"] = "valid intent"
637
+ plan["evidence"][0]["result"] = "\ud800"
638
+ Path(sys.argv[2]).write_text(json.dumps(plan, ensure_ascii=True, indent=2) + "\n", encoding="utf-8")
639
+ PY
640
+ printf ' next' >"$APPEND"
641
+ surrogate_before="$(digest "$TMP/surrogate-plan.json")"
642
+ set +e
643
+ out="$(python3 "$UPDATER" --plan "$TMP/surrogate-plan.json" --append-intent-file "$APPEND" --expected-sha256 "$surrogate_before" 2>&1)"
644
+ rc=$?
645
+ set -e
646
+ assert_rc "$rc" 2 "unencodable plan content must fail closed"
647
+ assert_contains "updated_plan_invalid_unicode" "$out" "surrogate reason"
648
+ [ "$(digest "$TMP/surrogate-plan.json")" = "$surrogate_before" ] || fail "surrogate refusal changed the plan"
649
+
650
+ # Pre-commit file-sync and replace faults leave the original target intact and
651
+ # remove every temporary file; no sensitive plan copy survives the failed call.
652
+ python3 - "$UPDATER" "$TMP" <<'PY'
653
+ import importlib.util
654
+ import json
655
+ import os
656
+ import stat
657
+ import sys
658
+ from pathlib import Path
659
+
660
+ updater_path, root = Path(sys.argv[1]), Path(sys.argv[2])
661
+ spec = importlib.util.spec_from_file_location("intent_updater_faults", updater_path)
662
+ module = importlib.util.module_from_spec(spec)
663
+ assert spec.loader is not None
664
+ spec.loader.exec_module(module)
665
+
666
+
667
+ def exercise(kind: str) -> None:
668
+ plan_path = root / f"{kind}-fault-plan.json"
669
+ append_path = root / f"{kind}-fault-append.txt"
670
+ plan = {
671
+ "intent": "initial fault-test intent",
672
+ "acceptance": ["The original target survives a pre-commit fault."],
673
+ "self_review": [],
674
+ "evidence": [{"id": "focused-test", "result": "A detailed pre-commit fault-injection result."}],
675
+ }
676
+ plan_path.write_text(json.dumps(plan, indent=2) + "\n", encoding="utf-8")
677
+ append_path.write_text(" next", encoding="utf-8")
678
+ before = plan_path.read_bytes()
679
+ real_fsync = module.os.fsync
680
+ real_replace = module.os.replace
681
+ if kind == "fsync":
682
+ def fail_regular_sync(fd: int) -> None:
683
+ if stat.S_ISREG(os.fstat(fd).st_mode):
684
+ raise OSError("synthetic file fsync failure")
685
+ real_fsync(fd)
686
+
687
+ module.os.fsync = fail_regular_sync
688
+ else:
689
+ def fail_replace(source: object, target: object) -> None:
690
+ raise OSError("synthetic replace failure")
691
+
692
+ module.os.replace = fail_replace
693
+ try:
694
+ module.run(["--plan", str(plan_path), "--append-intent-file", str(append_path)])
695
+ except module.UpdateError as exc:
696
+ assert exc.reason == "plan_write_failed", exc.reason
697
+ else:
698
+ raise AssertionError(f"{kind} fault did not fail")
699
+ finally:
700
+ module.os.fsync = real_fsync
701
+ module.os.replace = real_replace
702
+ assert plan_path.read_bytes() == before, f"{kind} fault changed the target"
703
+ assert not list(root.glob(f".{plan_path.name}.*")), f"{kind} fault leaked a temporary plan"
704
+
705
+
706
+ exercise("fsync")
707
+ exercise("replace")
708
+ PY
709
+
710
+ # Once rename succeeds, a directory-sync failure is a committed-but-unknown
711
+ # durability outcome. It carries the new digest and forbids a blind retry.
712
+ python3 - "$UPDATER" "$TMP/durability-plan.json" "$TMP/durability-append.txt" <<'PY'
713
+ import importlib.util
714
+ import json
715
+ import os
716
+ import stat
717
+ import sys
718
+ from pathlib import Path
719
+
720
+ updater_path, plan_path, append_path = map(Path, sys.argv[1:])
721
+ spec = importlib.util.spec_from_file_location("intent_updater_under_test", updater_path)
722
+ module = importlib.util.module_from_spec(spec)
723
+ assert spec.loader is not None
724
+ spec.loader.exec_module(module)
725
+ plan = {
726
+ "intent": "initial intent",
727
+ "acceptance": ["The candidate behavior is verified."],
728
+ "self_review": [],
729
+ "evidence": [{"id": "focused-test", "result": "A sufficiently detailed result for this fault injection."}],
730
+ }
731
+ plan_path.write_text(json.dumps(plan, indent=2) + "\n", encoding="utf-8")
732
+ append_path.write_text(" next", encoding="utf-8")
733
+ real_fsync = module.os.fsync
734
+
735
+ def fail_directory_sync(fd):
736
+ if stat.S_ISDIR(os.fstat(fd).st_mode):
737
+ raise OSError("synthetic directory fsync failure")
738
+ return real_fsync(fd)
739
+
740
+ module.os.fsync = fail_directory_sync
741
+ try:
742
+ module.run(["--plan", str(plan_path), "--append-intent-file", str(append_path)])
743
+ except module.UpdateError as exc:
744
+ assert exc.reason == "plan_committed_durability_unknown", exc.reason
745
+ assert "sha256=" in exc.detail and "do not retry blindly" in exc.detail
746
+ else:
747
+ raise AssertionError("directory fsync fault did not fail")
748
+ updated = json.loads(plan_path.read_text(encoding="utf-8"))
749
+ assert updated["intent"] == "initial intent next"
750
+ PY
751
+
752
+ # A stdout pipe closed before the flushed success receipt must exit nonzero
753
+ # with plan_committed_receipt_lost while the plan file keeps the committed
754
+ # update; rc 0 with a lost receipt reads as "no update happened".
755
+ python3 - "$UPDATER" "$TMP/closed-pipe-plan.json" "$TMP/closed-pipe-append.txt" <<'PY'
756
+ import json
757
+ import os
758
+ import subprocess
759
+ import sys
760
+ from pathlib import Path
761
+
762
+ updater_path, plan_path, append_path = map(Path, sys.argv[1:])
763
+ plan = {
764
+ "intent": "initial intent",
765
+ "acceptance": ["The candidate behavior is verified."],
766
+ "self_review": [],
767
+ "evidence": [
768
+ {"id": "focused-test", "result": "A sufficiently detailed result for closed-pipe coverage."}
769
+ ],
770
+ }
771
+ plan_path.write_text(json.dumps(plan, indent=2) + "\n", encoding="utf-8")
772
+ append_path.write_text(" next", encoding="utf-8")
773
+ read_fd, write_fd = os.pipe()
774
+ os.close(read_fd)
775
+ result = subprocess.run(
776
+ [sys.executable, str(updater_path), "--plan", str(plan_path), "--append-intent-file", str(append_path)],
777
+ stdout=write_fd,
778
+ stderr=subprocess.PIPE,
779
+ text=True,
780
+ )
781
+ os.close(write_fd)
782
+ assert result.returncode == 2, result.returncode
783
+ assert "plan_committed_receipt_lost" in result.stderr, result.stderr
784
+ assert "re-read the plan" in result.stderr, result.stderr
785
+ committed = json.loads(plan_path.read_text(encoding="utf-8"))
786
+ assert committed["intent"] == "initial intent next", committed["intent"]
787
+ PY
788
+
789
+ echo "test_update_review_plan_intent: ok"